Tinklaraščio įrašas
29/5/2025

„Slankiojančio paryškinimo“ naršymo juostos kūrimas su „JavaScript“ ir CSS

Šioje pamokoje Blake Lundquist mus supažindina su dviem metodais, kaip sukurti „slankiojančio paryškinimo“ naršymo modelį naudojant tik gryną „JavaScript“ ir CSS. Pirmasis metodas naudoja getBoundingClientRect metodą, kad aiškiai animuotų rėmelį tarp naršymo juostos elementų, kai jie yra paspaudžiami. Antrasis metodas pasiekia tą patį funkcionalumą naudojant naująjį „View Transition“ API.

Neseniai aptikome seną „jQuery“ pamoką, demonstruojančią „slankiojančio paryškinimo“ naršymo juostą, ir nusprendėme, kad šiai koncepcijai reikia modernaus atnaujinimo. Naudojant šį modelį, rėmelis aplink aktyvų naršymo elementą animuojasi tiesiogiai nuo vieno elemento prie kito, kai vartotojas spusteli meniu punktus. 2025 metais turime daug geresnių įrankių DOM manipuliavimui naudojant gryną „JavaScript“. Naujos funkcijos, tokios kaip „View Transition“ API, leidžia lengviau pasiekti laipsnišką tobulinimą (angl. progressive enhancement) ir pasirūpina daugybe animacijos smulkmenų.

„Judančio žymėjimo“ navigacijos juostos pavyzdys

Šioje pamokoje pademonstruosime du metodus, kaip sukurti „slankiojančio paryškinimo“ naršymo juostą naudojant gryną „JavaScript“ ir CSS. Pirmasis pavyzdys naudoja getBoundingClientRect metodą, kad aiškiai animuotų rėmelį tarp naršymo juostos elementų, kai jie yra paspaudžiami. Antrasis pavyzdys pasiekia tą patį funkcionalumą naudojant naująjį „View Transition“ API.

Pradinis žymėjimas

Tarkime, kad turime vieno puslapio programą (SPA), kurioje turinys keičiasi neperkraunant puslapio. Pradinis HTML ir CSS yra standartinė naršymo juosta su papildomu div elementu, turinčiu id #highlight. Pirmajam naršymo elementui suteikiame klasę .active.

See the Pen Moving Highlight Navbar Starting Markup by Smashing Magazine (@smashingmag) on CodePen.

(Žr. CodePen Moving Highlight Navbar Starting Markup)

Šiai versijai mes išdėstysime #highlight elementą aplink elementą su .active klase, kad sukurtume rėmelį. Galime pasinaudoti absoliučiu pozicionavimu ir animuoti elementą per naršymo juostą, kad pasiektume norimą efektą. Iš pradžių jį paslėpsime už ekrano ribų, pridėdami left: -200px, ir įtrauksime transition stilius visoms savybėms, kad bet kokie elemento padėties ir dydžio pokyčiai vyktų palaipsniui.

#highlight {
  z-index: 0;
  position: absolute;
  height: 100%;
  width: 100px;
  left: -200px;
  border: 2px solid green;
  box-sizing: border-box;
  transition: all 0.2s ease;
}

Pridėkite standartinį įvykio apdorotoją paspaudimų sąveikoms

Norime, kad paryškinimo elementas animuotųsi, kai vartotojas pakeičia .active naršymo elementą. Pridėkime click įvykio apdorotoją prie nav elemento, tada filtruokime tik tuos įvykius, kuriuos sukėlė elementai, atitinkantys mūsų norimą selektorių. Šiuo atveju norime pakeisti .active naršymo elementą tik tada, kai vartotojas spusteli nuorodą, kuri dar neturi .active klasės.

Iš pradžių galime iškviesti console.log, kad įsitikintume, jog apdorotojas suveikia tik tada, kai tikimasi:

const navbar = document.querySelector('nav');

navbar.addEventListener('click', function (event) {
  // grįžti, jei paspaustas elementas neatitinka teisingo selektoriaus
  if (!event.target.matches('nav a:not(active)')) {
    return;
  }
  
  console.log('click');
});

Atidarykite savo naršyklės konsolę ir pabandykite spustelėti skirtingus naršymo juostos elementus. Turėtumėte matyti, kad „click“ yra registruojamas tik tada, kai pasirenkate naują naršymo juostos elementą.

Dabar, kai žinome, kad mūsų įvykio apdorotojas veikia su teisingais elementais, pridėkime kodą, kuris perkels .active klasę į paspaustą naršymo elementą. Galime naudoti į įvykio apdorotoją perduotą objektą, kad rastume elementą, kuris inicijavo įvykį, ir suteiktume tam elementui klasę .active, prieš tai pašalinę ją iš anksčiau aktyvaus elemento.

const navbar = document.querySelector('nav');
 
 navbar.addEventListener('click', function (event) {
   // grįžti, jei paspaustas elementas neatitinka teisingo selektoriaus
   if (!event.target.matches('nav a:not(active)')) {
     return;
   }
-  console.log('click');
+  document.querySelector('nav a.active').classList.remove('active');
+  event.target.classList.add('active');
  });

Mūsų #highlight elementas turi judėti per naršymo juostą ir pozicionuotis aplink aktyvų elementą. Parašykime funkciją, kuri apskaičiuos naują padėtį ir plotį. Kadangi #highlight selektoriui pritaikyti transition stiliai, jis judės palaipsniui, kai pasikeis jo padėtis.

Naudodami getBoundingClientRect, galime gauti informaciją apie elemento padėtį ir dydį. Apskaičiuojame aktyvaus naršymo elemento plotį ir jo poslinkį nuo tėvinio elemento kairiosios ribos. Tada priskiriame stilius paryškinimo elementui, kad jo dydis ir padėtis sutaptų.

// apdorotojas paryškinimo judėjimui
const moveHighlight = () => {
  const activeNavItem = document.querySelector('a.active');
  const highlighterElement = document.querySelector('#highlight');
  
  const width = activeNavItem.offsetWidth;

  const itemPos = activeNavItem.getBoundingClientRect();
  const navbarPos = navbar.getBoundingClientRect()
  const relativePosX = itemPos.left - navbarPos.left;

  const styles = {
    left: `${relativePosX}px`,
    width: `${width}px`,
  };

  Object.assign(highlighterElement.style, styles);
}

Iškvieskime mūsų naują funkciją, kai suveikia paspaudimo įvykis:

navbar.addEventListener('click', function (event) {
   // grįžti, jei paspaustas elementas neatitinka teisingo selektoriaus
   if (!event.target.matches('nav a:not(active)')) {
     return;
   }
   
   document.querySelector('nav a.active').classList.remove('active');
   event.target.classList.add('active');
+  moveHighlight();
 });

Galiausiai, iškvieskime funkciją iškart, kad rėmelis atsirastų už mūsų pradinio aktyvaus elemento, kai puslapis pirmą kartą įkeliamas:

// apdorotojas paryškinimo judėjimui
const moveHighlight = () => {
 // ...
}
// rodyti paryškinimą, kai puslapis įkeliamas
moveHighlight();

Dabar rėmelis juda per naršymo juostą, kai pasirenkamas naujas elementas. Pabandykite spustelėti skirtingas naršymo nuorodas, kad animuotumėte naršymo juostą.

See the Pen Moving Highlight Navbar by Smashing Magazine (@smashingmag) on CodePen.

(Žr. CodePen Moving Highlight Navbar)

Tam prireikė vos kelių eilučių gryno „JavaScript“ ir tai galima lengvai išplėsti, kad būtų atsižvelgta į kitas sąveikas, pavyzdžiui, mouseover įvykius. Kitoje dalyje išnagrinėsime, kaip pertvarkyti šią funkciją naudojant „View Transition“ API.

„View Transition“ API naudojimas

„View Transition“ API suteikia funkcionalumą kurti animuotus perėjimus tarp svetainės vaizdų. Po gaubtu API sukuria „prieš“ ir „po“ vaizdų momentines nuotraukas ir tada tvarko perėjimą tarp jų. Vaizdų perėjimai yra naudingi kuriant animacijas tarp dokumentų, suteikiant įgimtos programėlės tipo vartotojo patirtį, būdingą tokiems karkasams kaip Astro. Tačiau API taip pat suteikia apdorotojus, skirtus SPA stiliaus programoms. Mes jį naudosime, kad sumažintume reikalingo „JavaScript“ kiekį ir lengviau sukurtume atsarginį funkcionalumą.

Šiam metodui mums nebereikia atskiro #highlight elemento. Vietoj to, galime stilizuoti .active naršymo elementą tiesiogiai, naudojant pseudo-selektorius, ir leisti „View Transition“ API tvarkyti animaciją tarp „prieš“ ir „po“ vartotojo sąsajos būsenų, kai spustelimas naujas naršymo elementas.

Pradėsime atsikratydami #highlight elemento ir su juo susijusio CSS, pakeisdami jį stiliais nav a::after pseudo-selektoriui:

<nav>
-  <div id="highlight"></div>
   <a href="#" class="active">Home</a>
   <a href="#services">Services</a>
   <a href="#about">About</a>
   <a href="#contact">Contact</a>
 </nav>
- #highlight {
-  z-index: 0;
-  position: absolute;
-  height: 100%;
-  width: 0;
-  left: 0;
-  box-sizing: border-box;
-  transition: all 0.2s ease;
- }
+ nav a::after {
+  content: " ";
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  border: none;
+  box-sizing: border-box;
+ }

.active klasei įtraukiame view-transition-name savybę, taip atrakindami „View Transition“ API magiją. Kai tik suaktyvinsime vaizdo perėjimą ir pakeisime .active naršymo elemento vietą DOM, bus padarytos „prieš“ ir „po“ momentinės nuotraukos, o naršyklė animuos rėmelį per juostą. Savo vaizdo perėjimui suteiksime pavadinimą highlight, bet teoriškai galėtume suteikti bet kokį pavadinimą.

nav a.active::after {
  border: 2px solid green;
  view-transition-name: highlight;
}

Kai turime selektorių, kuriame yra view-transition-name savybė, vienintelis likęs žingsnis yra suaktyvinti perėjimą naudojant startViewTransition metodą ir perduoti atgalinio iškvietimo (angl. callback) funkciją.

const navbar = document.querySelector('nav');
// Pakeisti aktyvų naršymo elementą paspaudus
navbar.addEventListener('click', async  function (event) {

  if (!event.target.matches('nav a:not(.active)')) {
    return;
  }
  
  document.startViewTransition(() => {
    document.querySelector('nav a.active').classList.remove('active');

    event.target.classList.add('active');
  });
});

Aukščiau pateikta atnaujinta click apdorotojo versija. Užuot patys atlikę visus judančio rėmelio dydžio ir padėties skaičiavimus, „View Transition“ API viską padaro už mus. Mums tereikia iškviesti document.startViewTransition ir perduoti atgalinio iškvietimo funkciją, kuri pakeis elementą, turintį .active klasę!

Vaizdo perėjimo koregavimas

Šiame etape, spustelėjus naršymo nuorodą, pastebėsite, kad perėjimas veikia, tačiau matomos keistos dydžio problemos.

Perėjimas tarp vaizdų su dydžio problemomis

Šį dydžio neatitikimą sukelia kraštinių santykio (angl. aspect ratio) pokyčiai vaizdo perėjimo metu. Čia nesigilinsime, bet Jake Archibald turi išsamų paaiškinimą, kurį galite perskaityti, norėdami gauti daugiau informacijos. Trumpai tariant, norėdami užtikrinti, kad rėmelio aukštis išliktų vienodas per visą perėjimą, turime deklaruoti aiškų height ::view-transition-old ir ::view-transition-new pseudo-selektoriams, kurie atitinkamai atspindi statinę senojo ir naujojo vaizdo momentinę nuotrauką.

::view-transition-old(highlight) {
  height: 100%;
}
::view-transition-new(highlight) {
  height: 100%;
}

Atlikime galutinį kodo pertvarkymą, kad sutvarkytume kodą, perkeldami atgalinio iškvietimo funkciją į atskirą funkciją ir pridėdami atsarginį variantą, kai vaizdo perėjimai nėra palaikomi:

const navbar = document.querySelector('nav');
// pakeisti elementą, kuriam pritaikyta .active klasė
const setActiveElement = (elem) => {
  document.querySelector('nav a.active').classList.remove('active');
  elem.classList.add('active');
}
// Pradėti vaizdo perėjimą ir perduoti atgalinio iškvietimo funkciją paspaudus
navbar.addEventListener('click', async  function (event) {
  if (!event.target.matches('nav a:not(.active)')) {
    return;
  }

  // Atsarginis variantas naršyklėms, kurios nepalaiko View Transitions:
  if (!document.startViewTransition) {
    setActiveElement(event.target);
    return;
  }
  
  document.startViewTransition(() => setActiveElement(event.target));
});

Štai mūsų „View Transition“ pagrindu veikianti naršymo juosta! Stebėkite sklandų perėjimą, kai spustelite skirtingas nuorodas.

(Žr. CodePen Moving Highlight Navbar with View Transition)

Išvada

Anksčiau animacijoms ir perėjimams tarp svetainės vartotojo sąsajos būsenų reikėjo daug kilobaitų išorinių bibliotekų, kartu su išsamiu, painiu ir klaidų kupinu kodu, tačiau nuo to laiko grynas „JavaScript“ ir CSS įtraukė funkcijas, leidžiančias pasiekti įgimtos programėlės tipo sąveikas be didelių išlaidų. Mes tai pademonstravome, įgyvendindami „slankiojančio paryškinimo“ naršymo modelį dviem būdais: CSS perėjimais, derinant su getBoundingClientRect() metodu, ir „View Transition“ API.

Ištekliai