MediaRecorder API – sprawdź jak nagrywać z poziomu Web!

Spis treści

  1. Wprowadzenie
  2. Użycie MediaRecorder
    1. Jak działa MediaRecorder API?
    2. Pobieranie streamu z kamery
    3. Pobieranie streamu z udostępniania ekranu
    4. Pobieranie streamu z canvasa
    5. Klikalny przykład na Github Pages
    6. Pełny kod przykładu na Github
  3. Podsumowanie

Wprowadzenie

Sporo osób nie wie o istnieniu MediaRecorder API – a umożliwia ono nagrywanie z poziomu przeglądarki internetowej. Warto znać to API na wypadek, gdy będziesz potrzebować zaimplementować np wysyłanie wiadomości głosowych czy wideo – możliwość stworzenia nagrania bezpośrednio z aplikacji może być uznana za wartościową funkcjonalność.

W tym wpisie sprawdzimy jak nagrać wideo z trzech źródeł:

  1. Kamera internetowa
  2. Pulpit użytkownika
  3. Canvas

Użycie MediaRecorder

Jak działa MediaRecorder API?

Konstruktor MediaRecorder przyjmuje jako pierwszy parametr stream który ma zostać nagrany, jako drugi typ mime nagrania:

recorder = new MediaRecorder(currentStream, { mimeType: 'video/webm' });

Przed utworzeniem recordera można upewnić się, że da się utworzyć nagranie tego typu, za pomocą metody isTypeSupported.

Same dane zbierane są za pomocą reakcji na event dataavailable:

recorder.addEventListener('dataavailable', e => {
  chunks.push(e.data);
});

Z tych danych zostanie potem utworzony obiekt Blob, który może zostać przykładowo pobrany bądź wysłany na serwer.

Przykład nasz zawierać będzie możliwość startu i zakończenia nagrania oraz pobrania nagrania:

document.querySelector('#startRecording')
  .addEventListener('click', () => {
    info.innerHTML = 'Recording...';
    recorder = new MediaRecorder(currentStream, { mimeType: 'video/webm' });
    recorder.addEventListener('dataavailable', e => {
      chunks.push(e.data);
    });
    recorder.start();
  });

document.querySelector('#stopRecording')
  .addEventListener('click', () => {
    info.innerHTML = 'Not recording';
    recorder.stop();
  });

document.querySelector('#downloadRecording')
  .addEventListener('click', () => {
    const blob = new Blob(chunks, { type: 'video/mp4' });
    // mało interesujący kod pobrania bloba
  });

Skąd jednak weźmie się stream ze zmiennej currentStream który zostanie nagrany?

Z trzech wspomnianych wcześniej źródeł:

  1. Kamera internetowa
  2. Pulpit użytkownika
  3. Canvas

A UI wyglądać będzie następująco:

MediaRecorder przykład
UI przykładu MediaRecorder

Trzy przyciski na górze ustawią odpowiednie źródło nagrania – a przyciski niżej umożliwią start/stop i pobranie.

Pobieranie streamu z kamery

Jeśli chcemy dostać się do streamu z urządzeń podłączonych do urządzenia, należy użyć metody getUserMedia – oczywiście tyczy się to również urządzeń wbudowanych.

W naszym przykładzie użytkownik może dokonać, za pomocą checkboxa, decyzji czy chce wybrać kamerę przednią („user”) czy tylną („environment”). Otwórz klikalny przykład (znajduje się dalej we wpisie) by przekonać się jak ten wybór działa:

currentStream = await navigator.mediaDevices.getUserMedia({
  video: {
    facingMode: {
      ideal: face.checked? 'user' : 'environment',
    },
  },
});
preview.srcObject = currentStream;

Ustawiamy również podgląd na elemencie wideo o id preview, by użytkownik widział co zostanie nagrane.

Pobieranie streamu z udostępniania ekranu

Udostępnienie ekranu do nagrywania odbędzie się za pomocą metody getDisplayMedia:

currentStream = await navigator.mediaDevices.getDisplayMedia({
  video: true,
});
preview.srcObject = currentStream;
const displaySurface = currentStream.getTracks()[0].getSettings().displaySurface;
subject.innerHTML = `Screen will be recorded${displaySurface?
  `, display surface: ${displaySurface}` : ''}`;

Dodatkowo, na przeglądarkach opartych na Chromium, mamy dostęp do tego co do udostępniania wybrał użytkownik – tab przeglądarki, aplikacja lub cały pulpit. Informacja ta dostępna jest w polu displaySurface.

Pobieranie streamu z canvasa

W przykładzie canvasa skorzystamy z przykładu biblioteki Konva.js:

https://konvajs.org/docs/sandbox/Free_Drawing.html

Dzięki któremu będziemy mogli na canvasie rysować linię.

Stream pobierzemy metodą captureStream:

currentStream = document.querySelector('canvas').captureStream();
preview.srcObject = currentStream;

Od tego momentu możemy zacząć rysować.

Klikalny przykład na Github Pages

https://radek-anuszewski.github.io/mediarecorder-api-demo/

Pełny kod przykładu na Github

https://github.com/radek-anuszewski/mediarecorder-api-demo

Podsumowanie

Zarówno MediaRecorder API jak i metody do urządzeń / udostępniania ekranu to tematy z którymi warto się zapoznać. Nagrywanie z poziomu aplikacji Web jest dziś proste, wspierane praktycznie wszędzie. A z dostępu do urządzeń korzysta coraz więcej aplikacji zapewniających łączność pomiędzy użytkownikami sieci.

Sprawdź jak walidować formularze w czystym JS!

Spis treści

  1. Wprowadzenie
  2. Walidowanie formularzy
    1. Baza pod przykłady
    2. Customowe wiadomości dla walidacji standardowej
    3. Customowy sposób prezentacji rezultatów walidacji
    4. Klikalny przykład na Github Pages
    5. Pełny kod przykładu na Github
  3. Podsumowanie

Wprowadzenie

Walidowanie formularzy nie zawsze jest prostym zadaniem – najczęściej wyłączamy domyślną walidację i piszemy bardzo customowy kod. Okazuje się jednak, że JS ma wbudowane Constraint Validation API a także umożliwia prostą zmianę domyślnych tekstów które pokazywane są użytkownikowi podczas domyślnej walidacji formularza.

Walidowanie formularzy

Baza pod przykłady

Bazą będzie formularz HTML zawierający pola różnego typu:

<form>
  <div class="form-group">
    <label for="name">
      Name
    </label>
    <input id="name" name="name" type="text" minlength="2" maxlength="10" required>
    <div class="form-group__error"></div>
  </div>
  <!-- Inne pola -->
</form>

Potrzebny nam będzie również checkbox za pomocą którego będziemy wybierać, czy chcemy użyć natywnej walidacji HTML czy też dodać atrybut novalidate do formularza i zawsze wywoływać funkcję submit:

<label for="nativeValidation">
  Native validation?
  <input type="checkbox" id="nativeValidation" checked />
</label>

const nativeValidationCheckbox = document
  .querySelector('#nativeValidation');
nativeValidationCheckbox
  .addEventListener('change', () => {
    form.toggleAttribute('novalidate', !nativeValidationCheckbox.checked);
  });

Customowe wiadomości dla walidacji standardowej

Jeśli odpowiada nam aktualny sposób walidacji który HTML ma wbudowany, możemy tylko zaktualizować wiadomości pokazywane użytkownikowi.

Zmiany tej możemy dokonać dzięki funkcji setCustomValidity.

Customową wiadomość ustawiać możemy na przykład w momencie, gdy użytkownik wpisuje dane w polu:

const validateInput = input => {
  const customValidity = getCustomValidity(input);
  input.setCustomValidity(customValidity);
}
    
form.addEventListener('input', e => {
  const input = e.target;
  input
    .parentElement
    .querySelector('.form-group__error').innerHTML = '';
  validateInput(input);
});

W jaki sposób natomiast stworzymy wiadomości? Bazując na interfejsie ValidityState, dzięki któremu każdy input zawiera informację o błędach walidacji:

const getCustomValidity = input => {
  const validity = input.validity;
  const name = input.name.charAt(0).toUpperCase() + input.name.slice(1);
  if (validity.valueMissing) {
    return `${name} is needed for account`;
  }
  if (validity.tooShort) {
    return `Please provide at least ${input.minLength} characters for ${name}`;
  }
  if (validity.tooLong) {
    return `Please provide at most ${input.maxLength} characters for ${name}`;
  }
  if (validity.rangeUnderflow) {
    return `Please provide value which is at least ${input.min} for ${name}`;
  }
  if (validity.rangeOverflow) {
    return `Please provide value which is at most ${input.max} for ${name}`;
  }
  if (validity.typeMismatch) {
    if (input.type === 'email') {
      return `Please provide a valid email - copy it from your mailbox`;
    }
    if (input.type === 'url') {
      `Please provide a valid url - copy it from browser address bar`;
    }
 }
 return '';
}

Dzięki temu, przykładowo jeśli podamy tylko jeden znak gdy wymagane są co najmniej dwa, pojawi się zdefiniowana przez nas wiadomość:

Customowy sposób prezentacji rezultatów walidacji

Co jednak, jeśli nie odpowiada nam natywna walidacja? Możemy wyłączyć ją dodając atrybut novalidate na formularzu. W takiej sytuacji walidować musimy ręcznie, np. w reakcji na wysłanie formularza. Do walidacji możemy reużyć sposobu z poprzedniego paragrafu bazującego na validity:

form.addEventListener('submit', e => {
  e.preventDefault();

  const inputs = Array
    .from(e.currentTarget)
    .filter(el => el.id); // to remove button

  for (const input of inputs) {
    const customValidity = getCustomValidity(input);
    if (customValidity) {
      input
        .parentElement
        .querySelector('.form-group__error')
        .innerHTML = customValidity;
      return;
    }
  }

  const values = inputs
    .reduce((acc, current) => ({
      ...acc,
      [current.id]: current.value,
   }), {});
  e.currentTarget.reset();
  alert(JSON.stringify(values));
});

W funkcji przechodzimy przez wszystkie inputy, jeżeli dla któregoś mamy wiadomość o błędzie walidacji wtedy wyświetlamy ją i wychodzimy z funkcji:

A jeśli błędów nie ma, wtedy za pomocą funkcji reduce transformujemy dane w formularzu do obiektu i wyświetlamy je alertem.

Klikalny przykład na Github Pages

Klikalny przykład znajduje się pod adresem:

https://radek-anuszewski.github.io/custom-validation-demo/

Pełny kod przykładu na Github

Pełen kod przykładu znajduje się pod adresem:

https://github.com/radek-anuszewski/custom-validation-demo

Podsumowanie

Walidując formularze nie zawsze musimy polegać na frameworkach czy bibliotekach – wbudowane w JS mechanizmy potrafią całkiem sporo i warto je znać oraz stosować.

Dowiedz się czemu warto czekać na pseudoklasę :has!

Spis treści

  1. Wprowadzenie
  2. Pseudoklasa :has
    1. Jak działa pseudoklasa :has?
    2. Jakie pseudoklasa :has ma wsparcie i jaką biblioteką go zasymulować?
  3. Użycie pseudoklasy :has do stylowania formularzy na bazie walidacji pól
    1. Stworzenie skryptu transformującego CSS
    2. Style odpowiadające za kolorystykę walidacji
    3. Wymagany kod JS
    4. Efekt działania aplikacji
    5. Klikalny przykład na Github Pages
    6. Pełny kod przykładu na Github
  4. Podsumowanie

Wprowadzenie

Stack Overflow pełen jest pytań o to, czy w CSS istnieje „parent selector”. O ile możemy stylować dzieci bazując na tym, kto jest rodzicem – w drugą stronę póki co to nie działa.
Na szczęście w specyfikacji znajduje się pseudoklasa :has – a dzięki sprytnej bibliotece możemy używać jej nawet dziś, gdy jeszcze żadna przeglądarka go nie wspiera.

Zapraszam do przeczytania wpisu o tym, jak za pomocą tego selektora stylować walidację formularzy.

Pseudoklasa :has

Jak działa pseudoklasa :has?

Pseudoklasa :has wybiera element zawierający określona zawartość. Przykładowo:

.class:has(.another-class)

Wybierze element z klasą .class tylko wtedy, gdzie wewnątrz elementu .class znajduje się element z klasą .another-class.

Struktura ta mogłaby wyglądać tak:

<div class="class">
   <p>
     <h2>
       Header
    </h2>
    <div class="another-class">
       Some content
    </div>
  </p>
</div>

Będzie więc mega użyteczny do wszelkiej maści stylowania bazującego na walidacji – gdzie np jeśli mamy element w stanie :valid cała grupa elementów jak opis, label itd ma być zielona.

Jakie pseudoklasa :has ma wsparcie i jaką biblioteką go zasymulować?

W momencie pisania tekstu pseudoklasa nie ma żadnego wsparcia.

Na szczęście do momentu implementacji wspomóc nas może CSS Has Pseudo, plugin do PostCSS i biblioteka w jednym.

Umożliwia ona zamianę pseudoklasy :has na odpowiadający jej selektor atrybutu i wygenerowanie pliku zawierającego te zmiany. Następnie, już w kodzie strony, wywołanie metody cssHasPseudo spowoduje, że to elementów które w CSS mają selektor atrybutu zostanie ten atrybut dodany.

Użycie pseudoklasy :has do stylowania formularzy na bazie walidacji pól

Stworzenie skryptu transformującego CSS

Aby pseudoklasa została przetransformowana do atrybutu, musimy utworzyć odpowiedni skrypt. Skrypt ten zawierać będzie uruchomienie PostCSS wraz z pluginem has-pseudo i zapisanie zmian do pliku:

const postcss = require('postcss');
const postcssHasPseudo = require('css-has-pseudo/postcss');
const fs = require('fs')

fs.readFile('index-to-transform.css', (err, css) => {
  postcss([postcssHasPseudo()])
    .process(css, {
      from: 'index-to-transform.css', 
      to: 'index.css' })
    .then(result => {
      fs.writeFile(
        'index.css', 
        result.css, 
        () => true)
    });
});

Style odpowiadające za kolorystykę walidacji

Nasze style opierać się będą bardzo mocno o CSS Variables – jeśli jeszcze ich nie znasz możesz poczytać o nich tutaj:

Stwórzmy bazowe style, zawierający kolory pod walidację formularza:

html {
  --color-regular: black;
  --color-error: red;
  --color-success: green;
  --validation-color: var(--color-regular);
}

Bazowym elementem do stylowania będzie klasa .form-group, na której ustawiać będziemy kolor walidacji, z którego korzystać będzie kod kolorujący poszczególne elementy:

.form-group {
  width: 200px;
  padding: 8px;
  border: 1px solid var(--validation-color);
  color: var(--validation-color);
}

Podobnie same pola formularza korzystać będą ze zmiennej --validation-color:

input {
  border: 1px solid var(--validation-color);
  color: inherit;
}

I w tym miejscu zaczyna się magia CSS Variables 🙂 Ponieważ zamiast zmieniać kolorystykę wszystkich elementów w reakcji na wynik walidacji, potrzebujemy zmienić tylko wartość jednej zmiennej:

.form-group:has(:invalid) {
  --validation-color: var(--color-error);
}

.form-group:has(:valid) {
  --validation-color: var(--color-success);
}

.form-group.pristine {
  --validation-color: var(--color-regular);
}

Po co potrzebujemy dodatkowej klasy .pristine? Ponieważ CSS nie pozwala nam napisać selektora tak, by obejmował tylko elementy, w których użytkownik już coś wpisał. Domyślnie więc wszystkie pola są w statusie invalid, jeśli są wymagane.

Wymagany kod JS

Kod JS będzie bardzo prosty – najistotniejszym elementem jest zawołanie funkcji bibliotecznej, która stworzy atrybuty na bazie pseudoklas:

cssHasPseudo(document);

W kodzie strony łatwo zauważymy efekt jej wywołania:

Pseudoklasy zmienione na atrybut
Pseudoklasy zmienione na atrybut

Potrzebować jeszcze będziemy obsługi formularza – usunięcia klasy .pristine gdy użytkownik wpisuje coś w pole oraz przywrócenia jej, gdy formularz jest czyszczony po submicie:

form
  .addEventListener('change', e => {
    e.target
      .closest('.form-group')
      .classList
      .remove('pristine');
    });

form
  .addEventListener('submit', e => {
    e.preventDefault();
    form.reset();
      [...document.querySelectorAll('.form-group')]
        .forEach(el => {
          el.classList.add('pristine');
        });
    });

Efekt działania aplikacji

Efektem tak prostego kodu i sprytnego wykorzystania pseudoklasy i CSS Variables jest łatwe w implementacji prezentowanie użytkownikowi stanu walidacji formularza:

:has - efekt użycia do kolorowania walidacji formularza
:has – efekt użycia do kolorowania walidacji formularza

Klikalny przykład na Github Pages

Klikalny przykład znajduje się pod adresem:

https://radek-anuszewski.github.io/has-pseudoclass-demo/

Pełny kod przykładu na Github

Pełny kod aplikacji znajduje się pod adresem:

https://github.com/radek-anuszewski/has-pseudoclass-demo

Podsumowanie

Choć pseudoklasa :has nie jest jeszcze oficjalnie nigdzie wspierana łatwo zobaczyć, że znajdzie się dla niej wiele przypadków użycia. Połączona z nowoczesnymi funkcjonalnościami CSS jak CSS Variables uprości wiele sytuacji, które bez tego wymagałyby osobnych bibliotek czy też dodatkowego kodu JS.

Dlatego warto trzymać rękę na pulsie w sprawie tego, jaka jest przyszłość tej pseudoklasy.

CSS Variables – czemu warto je znać?

Spis treści

  1. Wprowadzenie
  2. Czym są CSS Variables?
  3. Bardzo praktyczne użycie CSS Variables
    1. Implementacja edytora wizualnego do CSS Variables
    2. Klikalny przykład na Github Pages
    3. Pełen kod przykładu na Github
  4. Podsumowanie

Wprowadzenie

Przed pojawieniem się CSS Custom Properties (znane też jako CSS Variables) implementacja takich funkcjonalności jak różne motywy, tryb ciemny czy danie możliwości konfigurowania kolorystyki dynamicznie była dość uciążliwa. Preproesory takie jak SCSS wspomagały ten proces poprzez własną implementację zmiennych i możliwość pisania funkcji generujących np różne klasy dla różnych motywów – ale efektem takich działań była spora ilość generowanego kodu, który musiał powstać by zrekompensować brak dynamiki.

CSS Variables pozwalają zmieniać wartości w stylach w pełni dynamicznie – a ten post pokaże, do czego tą właściwość można wykorzystać.

Czytaj dalej CSS Variables – czemu warto je znać?

React Query – jak zarządzać stanem serwerowym

Spis treści

  1. Wprowadzenie
  2. Fejkowe API dzięki ServiceWorker
  3. Zastosowanie React Query
    1. React Query a lista postów
      1. useQuery w liście postów
      2. useMutation w liście postów
    2. Lista komentarzy w poście
      1. useQuery w liście komentarzy
      2. useMutation w liście postów
    3. Pełen przykład React Query na Github
    4. Klikalny przykład React Query na Github Pages
  4. Podsumowanie

Wprowadzenie

Dlaczego warto poznać React Query?

Do tej pory w projektach które tworzyłem do zarządzania stanem używany był głównie Redux. Lądowały w nim dane z serwera, stan urządzeń jak np wybrana przez użytkownika kamera i mikrofon czy różne dane potrzebne w wielu miejscach aplikacji jak uprawnienia użytkownika w systemie.

Aplikacje te miały dość rozbudowany stan kliencki, do przechowywania którego Redux jest stworzony. Stan „serwera”, czyli dane naszej aplikacji jest inny w swoim zachowaniu. Zmienia się bez wiedzy frontendu, może być z jakiegoś powodu niedostępny, łatwo też stracić synchronizację z danymi z serwera, które mogą się zmieniać w trakcie pracy klienta.

Stąd, jeśli stan aplikacji to głównie stan serwera a nie stan kliencki, rozsądne wydaje się zastosowanie biblioteki React Query. Zatroszczy się ona o cache danych, przechowywanie w aplikacji i aktualizację na serwerze.

Czytaj dalej React Query – jak zarządzać stanem serwerowym

Poznaj querySelector i przestań używać getElementsBy…

btw – kod może Cię zaskoczyć!

Spis treści

  1. Wprowadzenie
  2. QuerySelector/querySelectorAll
    1. Kilka słów o querySelector
    2. Słówko o querySelectorAll
    3. Dwa słowa o pewnej nieścisłości
    4. Kod przykładu na Github
    5. Klikalny przykład na Github Pages
  3. Dlaczego querySelector jest lepszy od innych metod?
  4. Podsumowanie

Wprowadzenie

Lat temu bardzo dużo główną przewagą jQuery nad czystym JS było to, że ujednolicała i upraszczała zachowanie pomiędzy przeglądarkami. JS jednak zaczął nadrabiać stracone lata, modernizując swoje API, większy nacisk również został położony na kompatybilność między przeglądarkami.

Jedną z rzeczy które najbardziej lubię w jQuery jest możliwość używania selektorów do pobierania elementu, np $('.form .element:not(.inactive)').

I choć JavaScript od wielu lat daje możliwość używania selektorów przy pobieraniu elementów DOM za pomocą querySelector/querySelectorAll, bardzo często widzę użycia innych metod pobierania elementów, jak getElementById czy getElementsByTagName.

W tym wpisie będę chciał przekonać Cię, że querySelector/querySelectorAll to zdecydowanie lepszy wybór 🙂

Czytaj dalej Poznaj querySelector i przestań używać getElementsBy…