Świetlana przyszłość stylów – podświetlanie z CSS Custom Highlight API

Spis treści

  1. CSS Custom Highlight API
  2. Jak to działa? CSS Custom Highlight API krok po kroku
  3. CSS Custom Highlight API – przykład z życia wzięty
  4. Pełen kod przykładu na Github
  5. Działająca aplikacja na Github Pages
  6. Podsumowując…

CSS Custom Highlight API

Czy pamiętasz te frustrujące chwile gdy aby podświetlić tekst na stronie musiałeś dodawać tagi takie jak mark i dodawać do nich style CSS?

Było to uciążliwe ponieważ wymuszało na Tobie cięcie tekstu i zmiany w treści.

A może miałeś problemy jako użytkownik jakiejś aplikacji? W sytuacji, gdy np Twój edytor tekstu wkleił w Twój dokument zbędne tagi i style.

Z odsieczą przychodzi CSS Custom Highlight API, które pozwoli Ci programistycznie określić zakres tego co chcemy podświetlić a potem ostylować to w CSS.

Zbuduj ze mną demo zawierające 2 sekcje:

  1. Sekcja pierwsza, z podstawowym przykładem podświetlania
  2. Sekcja druga, z przykładem który wnosi już rzeczywistą funkcjonalność

Jeśli zainteresuje Cię pełny kod implementacji – dostępne będzie zarówno repo na Github jak i działająca wersja na Github Pages

Tak będzie ono wyglądać:

CSS Custom Highlight API - wygląd przykładu
CSS Custom Highlight API – wygląd przykładu

Jak to działa? CSS Custom Highlight API krok po kroku

By podświetlić fragment tekstu, potrzebujesz stworzyć obiekt Range.

Chcemy pokolorować tekst “Check pink box to make me pink” na różowo lub “check blue box to make me blue” na niebiesko.

Tekst ten da się uogólnić do Check ${color} box to make me ${color}.

Musisz więc stworzyć zakres od check ${color} do me ${color}:

const text = document.querySelector('#text');

const getRangeBound = (color) => {
  const textContent = text.textContent.toLowerCase();
  const startPosition = textContent.indexOf(`check ${color}`);
  const endText = `me ${color}`;
  const endPosition = textContent.indexOf(endText) + endText.length;
  return { startPosition, endPosition };
}

Mając początek i koniec zakresu stworzymy obiekt Range:

const getHighlightRange = (color) => {
  const range = new Range();
  const { startPosition, endPosition } = getRangeBound(color);
  range.setStart(text.firstChild, startPosition);
  range.setEnd(text.firstChild, endPosition);
  return range;
}

Po czym zostały Ci do zaimplementowania jeszcze 2 rzeczy:

  1. Wyrejestrować podświetlenie metodą CSS.highlights.delete jeśli wybrany kolor zosta odznaczony
  2. Zarejestrować podświetlenie w rejestrze metodą CSS.highlights.set

A metodę która to robi możesz podpiąć do checkboxów:

const toggleHighlight = (color, checked) => {
  if (!checked) {
    CSS.highlights.delete(color);
    return;
  }

  const range = getHighlightRange(color);
  CSS.highlights.set(color, new Highlight(range));
}

checkboxes.forEach(checkbox => {
  checkbox.addEventListener('change', e => {
    const color = e.target.id;
    toggleHighlight(color, e.target.checked);
  })
});

W ostylowaniu podświetlonego zakresu pomoże Ci ::highlight(register highlight name):

::highlight(pink) {
  background-color: deeppink;
  color: white;
}

::highlight(blue) {
  background-color: blue;
  color: white;
}

Efekt obu zaznaczonych checkboxów wygląda tak:

CSS Custom Highlight API - prosty przykład użycia
CSS Custom Highlight API – prosty przykład użycia

Dzięki stylowaniu po zarejestrowanej nazwie podświetlenia, nie kolidują one ze sobą.


CSS Custom Highlight API – przykład z życia wzięty

Gdy wiesz już jak działa CSS Custom Highlight API, możesz zbudować coś co bardziej przyda się w prawdziwym życiu.

Powiedzmy, że potrzebujesz przefiltrować tabelę. Dobrze byłoby podświetlić miejsce w którym występuje szukany tekst, by łatwiej odnaleźć kontekst w jakim się on pojawia.

API pozwala zarejestrować wiele podświetlonym zakresów pod jedną nazwą, dzięki czemu da się podświetlić wiele wystąpień danego tekstu na raz.

Na potrzeby przypadku stwórzmy prostą tablicę zarządzaną z poziomu JS:

Tablica do filtrowania
Tablica do filtrowania

Dane do tablicy są zahardkodowane a funkcja wyświetlająca tablice wygląda tak:

const fillTable = users => {
  tableBody.innerHTML = users.map(user => (
    `<tr>
      <td>${user.name}</td>
      <td>${user.surname}</td>
      <td>${user.occupation}</td>
    </tr>`
  )).join('');
};
fillTable(users) // initial display

Tabelka będzie filtrowana danymi z pola – użytkownik pojawi się na liście tylko wtedy gdy w jednym z pól które obiekt posiada znajduje się szukany fragment:

searchField.addEventListener('input', e => {
  const searchString = e.target.value.toLowerCase();
  if (!searchString) {
    CSS.highlights.delete("search-results");
    fillTable(users);
  }
  const filteredUsers = filterUsers(users, searchString);
  fillTable(filteredUsers);
  highlightSearchText(searchString);
});

Sama funkcja highlightSearchText przegląda całą treść tabelki w poszukiwaniu tekstu zawierającego szukany fragment i jest mocno skomplikowana.

Została skopiowana z przykładu z MDN a potem nieco zmodyfikowana.

Filtrowanie wygląda tak:

Przefiltrowana tablica
Przefiltrowana tablica

W CSS znajdują się style które dodają zielone tło tekstu:

::highlight(search-results) {
  background-color: green;
  color: white;
}

Zauważ, że na stronie może istnieć wiele różnych podświetleń i nie interferują one ze sobą:

Ostateczny wygląd przykładu CSS Custom Highlight API
Ostateczny wygląd przykładu CSS Custom Highlight API

Pełen kod przykładu na Github

Kod przykładu znajdziesz pod adresem:

https://github.com/radek-anuszewski/css-custom-highlight-api


Działająca aplikacja na Github Pages

Działającą aplikację znajdziesz pod adresem:

https://radek-anuszewski.github.io/css-custom-highlight-api/


Podsumowując…

Dzięki CSS Custom Highlight API możesz efektywniej oznaczać tekst na stronach.

Dla Ciebie to brak zabawy z modyfikacją zawartości tekstowej przy podświetlaniu, dla użytkownika mniej problemów w codziennych sytuacjach gdy kopiuje fragmenty tekstu.

Co prawda póki co brak wsparcia poza przeglądarkami Chromium, ale pozytywny feedback od użytkowników zmotywuje innych vendorów do implementacji tego API u siebie.