Im dłużej przyglądałem się bloczkom kodu na blogu, tym bardziej wydawało mi się, iż czegoś w nich brakuje. W końcu doszło do mnie: nie ma numeracji linii! Pora to naprawić.
Wymogi dla numeracji
Jednak jeżeli już dodaję numerację, to chciałbym, żeby spełniała kilka wymagań:
- Numery linii nie powinny się kopiować razem z kodem. Innymi słowy: nie powinny być częścią treści bloczka kodu.
- Numeracja nie powinna dodawać śmieci w HTML-u. Mogę na chama dodać element, w który wepcham wygenerowane numery, ale chciałbym tego uniknąć. Idealnie, jakby w kodzie nie pojawił się żaden nowy element.
- Numery nie powinny się przewijać razem z kodem. Tak to działa w praktycznie każdym edytorze kodu: sam kod się przewija horyzontalnie, ale numer linii jest zawsze widoczny.
- Numeracja powinna się pokazywać, gdy jest odpowiednio dużo miejsca na nią. jeżeli ktoś przegląda bloga na wąskim ekranie, na którym widać raptem kilka znaków w linii, dodanie dodatkowo numeracji całkowicie uniemożliwi mu zapoznanie się z kodem.
Ok, skoro wymagania już mamy, możemy zająć się ich spełnianiem!
Nie kopiujemy i nie śmiecimy
Pierwsze dwa wymogi z powyższej listy tak naprawdę załatwić można za jednym zamachem. Skoro nie chcemy śmiecić w HTML-u, to zostaje nam… śmiecenie w CSS-ie lub JS-ie. W tym pierwszym od praktycznie zawsze jest możliwość dodawania treści przy pomocy pseudoelementów ::before i ::after oraz własności content. Kolejnym krokiem jest ustalenie, gdzie te style dodać.
Do kolorowania kodu w bloczkach używam Shiki.
Dygresja
Shiki generuje cały potrzebny kod po stronie serwera, dzięki czemu przeglądarka dostaje już całość pokolorowaną. Stoi to w opozycji do większości innych rozwiązań (jak Prism.js czy zdobywający popularność MicroLighter), które robią to po stronie przeglądarki.
Osobiście uważam, iż kolorowanie po stronie serwera jest lepsze dla osoby korzystającej ze strony. Nie dość, iż przeglądarka nie musi wykonywać dodatkowej pracy (co może być problemem przy dużej liczbie dużych bloków kodu na stronie wyświetlanej na słabszym urządzeniu), to dodatkowo kod będzie pokolorowany zawsze. Czyli choćby wtedy, gdy JS nie zadziała.
Shiki jest na tyle miły, iż każdą linię kodu otacza w element span.line, a cały bloczek ma klasę . shiki. Dzięki temu wystarczy dodać pseudoelement z numeracją bezpośrednio do linii:
Dla każdego elementu .line wewnątrz elementu .shiki dodaję pseudoelement ::before (1). W nim używam własności content, by dodać cyfrę 1 (2). Dodatkowa wartość w tej własności, po znaku ukośnika, to tekst alternatywny (podobnie jak w przypadku obrazków). W tym przypadku to pusty ciąg tekstowy. Dzięki temu technologia asystująca (np. czytnik ekranu) będzie ignorowała numerację. Dodatkowo zastosowanie tzw. generowanej treści w CSS-ie sprawia, iż przy kopiowaniu numeracja zostanie pominięta.
Automatyczna numeracja
No dobrze, ale jak sprawić, żeby linie faktycznie się numerowały? Na ten moment wszystkie mają ustawiony na sztywno numer 1:

Kliknij obrazek, aby go powiększyć
Tutaj na pomoc przychodzą liczniki! Dzięki nim możemy powiedzieć CSS-owi, żeby dla wszystkich bloczka kodu stworzył nowy licznik, a dla każdej linii w tym bloczku – zwiększył o jeden jego wartość.
Na początku tworzymy nowy licznik o nazwie code-line (1) dla wszystkich elementu .shiki. Potem, w każdym pseudoelemencie .line::before zwiększamy jego wartość o 1 (2), a następnie wyświetlamy przy pomocy funkcji counter() (3).
Tym prostym sposobem linie zaczęły się automatycznie numerować:

Kliknij obrazek, aby go powiększyć
Nie przewijamy, ale stylujemy
No dobrze, dodajmy trochę stylów, bo na razie numeracja wygląda słabo. Przy okazji od razu naprawimy problem z przewijaniem się numerków razem z kodem:
Trochę się tego nazbierało. Więc po kolei:
- Dzięki ustawieniu display: inline-block będziemy mogli nadawać numeracji szerokość oraz marginesy.
- Pozycja ustawiona na sticky oznacza, iż ten element ma zostać w miejscu, gdy bloczek będzie przewijany.
- Ta własność to logiczny odpowiednik left (lub right, jeżeli strona jest w języku zapisywanym od prawej do lewej). W połączeniu z position: sticky oznacza tyle, iż numeracja ma się “przykleić” do swojego miejsca, gdy w trakcie przewijania dotknie lewej krawędzi bloczka.
- To z kolei logiczny odpowiednik width. Nasza numeracja zawsze będzie miała szerokość 4 znaków. Dzięki temu numerek choćby dla długiego kodu na pewno się zmieści.
- Dodajemy też obramowanie za numeracją, żeby oddzielić ją od samego kodu. Nie podajemy koloru, dzięki czemu domyślnie zostanie użyty aktualny kolor fonta.
- Żeby kod nie “przykleił się” do obramowania numeracji, dodajemy margines za nią.
- Nie chcemy też, żeby sama numeracja “przyklejała się” do obramowania, więc dodajemy jej odpowiedni margines wewnętrzny.
- Wymuszamy, żeby numeracja miała takie samo tło jak linia. W moich stylach dla kodu każdy span wewnątrz kodu kolorowanego przez Shiki ma nadane tło. A iż tło się nie dziedziczy automatycznie w CSS-ie, wymuszamy to manualnie. jeżeli nie nadalibyśmy tła dla numeracji, byłoby przezroczyste i numeracja nakładałaby się na kod w trakcie przewijania:

Kliknij obrazek, aby go powiększyć
- I wreszcie – wyrównujemy numerek do końca (w tym przypadku – do prawej).
Dodatkowo usunąłem margines wewnętrzny dla bloczka kodu, inaczej kod z numeracją wyglądał… dziwnie:

Kliknij obrazek, aby go powiększyć
Ostateczny efekt wygląda zadowalająco:

Kliknij obrazek, aby go powiększyć
Nie zasłaniamy
Pozostaje ostatni problem – numeracja na wąskich ekranach może przysłaniać kod:

Kliknij obrazek, aby go powiększyć
I tak, zdaję sobie sprawę z tego, iż to ekstremalny przykład, ale na szczęście naprawienie tego nie jest szczególnie trudne. Dzięki container queries można sprawić, iż numeracja pokaże się tylko wówczas, gdy bloczek kodu będzie miał szerokość co najmniej 20 znaków:
Z bloczka kodu tworzymy kontener (1). Wskazujemy, iż interesuje nas jedynie jego wymiar liniowy (szerokość). Następnie, dla kontenera o szerokości poniżej 20 znaków (2) ukrywamy numerację (3). Style te wstawiamy po ogólnych stylach numeracji (4) – dzięki temu mamy pewność, iż je nadpiszą.
Od teraz bardzo wąskie bloczki kodu nie będą miały numeracji:

Kliknij obrazek, aby go powiększyć
Pojawił się jednak inny problem – kontener zepsuł numerację. Nagle każda linia ma ten sam numer, 1:

Kliknij obrazek, aby go powiększyć
Na szczęście łatwo to naprawić – wystarczy resetować licznik nie w samym bloczku (kontenerze), a w pierwszej linii w danym bloczku kodu:
Dygresja
Pseudoklasa :first-child działa tutaj, ponieważ linie są bezpośrednimi dziećmi bloczku kodu. W innym przypadku znalezienie pierwszej linii mogłoby być trudniejsze.
Ostatni szlif
Na dołączonych wyżej zrzutach ekranu dostrzec można jeszcze jeden, subtelny problem: każda ostatnia linia w bloczku jest pusta. Wyświetla się wyłącznie dlatego, iż teraz jest dodana do niej numeracja. W innym wypadku – jako pusty element liniowy – linia “zapadłaby się” i nie byłaby wyświetlona. Jest na to proste rozwiązanie: nie wyświetlać numeracji dla ostatniej linii, jeżeli jest ona pusta.
Dygresja
W idealnym świecie zmieniłbym konfigurację Shiki tak, aby taka linia nie była generowana… Ale rozwiązanie CSS-owe jest zdecydowanie prostsze.
Numeracja w akcji
I tym sposobem do bloga dodaliśmy numerację! Jak działa w praktyce, zobaczyć można na poniższym bloczku:



