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.