Wpadki i wypadki #13

Dzisiaj chyba najkrótszy odcinek Wpadek i wypadków w historii, poświęcony animacjom.

Bardzo częstym motywem pojawiającym się na stronach internetowych są wszelkiego rodzaju animacje „wjeżdżania” i pojawiania się treści, które odpalają się wraz z tym, jak użytkownik przewija stronę. Jedną z bibliotek umożliwiających tego typu animację jest AOS, na podstawie którego przygotowałem prymitywny przykład. Wszystko wydaje się być w jak najlepszym porządku, ale… czy na pewno?

Najpoważniejszym i prawdopodobnie najczęstszym problemem z tego typu efektem jest ignorowanie preferencji użytkownika dotyczących ograniczania ruchu, co może doprowadzić do wystąpienia różnych objawów u części użytkowników. Przyznaję, zadbanie o to to coś, co autorzy tego typu bibliotek powinni wbudować w swoje rozwiązania. Dopóki jednak się tak nie stanie, wypada zadbać o to samemu. Najprostszym sposobem jest zastosowanie API matchMedia w połączeniu z media query prefers-reduced-motion. Dzięki temu wykryjemy w JS-ie, czy user życzy sobie animacje i wczytamy (lub nie) odpowiedni skrypt.

Innym problemem jest fakt, że tego typu skrypty najczęściej sprawiają, że strona jest nieużywalna, jeśli JS nie zadziała. Dzieje się tak dlatego, że style są aplikowane od razu, niezależnie od tego, czy skrypt się wczytał i odpalił, czy nie. W przypadku biblioteki AOS można temu łatwo zaradzić, wczytując arkusz dopiero wtedy, gdy wiemy, że skrypt się wczytał.

Przykładowe rozwiązanie może używać import()/dynamicznie stworzonego elementu script do wczytania skryptu, a następnie dynamicznie tworzyć arkusz stylów.

Wersja z import():

( async function() {
	const isReducedMotion = window.matchMedia( '( prefers-reduced-motion )' ).matches;

	if ( isReducedMotion ) {
		return;
	}

	await import( 'https://unpkg.com/aos@2.3.1/dist/aos.js' );

	const stylesheet = document.createElement( 'link' );

	stylesheet.addEventListener( 'load', AOS.init );
	stylesheet.href = 'https://unpkg.com/aos@2.3.1/dist/aos.css';
	stylesheet.rel = 'stylesheet';
	
	document.head.append( stylesheet );
}() );

Niektóre biblioteki (jak AOS) nie działają jednak z import() i dla nich można wykorzystać wersję z dynamicznie tworzonym skryptem:

( function() {
	const isReducedMotion = window.matchMedia( '( prefers-reduced-motion )' ).matches;

	if ( isReducedMotion ) {
		return;
	}

	const script = document.createElement( 'script' );

	script.addEventListener( 'load', () => {
		const stylesheet = document.createElement( 'link' );

		stylesheet.addEventListener( 'load', AOS.init );
		stylesheet.href = 'https://unpkg.com/aos@2.3.1/dist/aos.css';
		stylesheet.rel = 'stylesheet';
		
		document.head.append( stylesheet );
	} );

	script.src = 'https://unpkg.com/aos@2.3.1/dist/aos.js';

	document.head.append( script );
}() );

Innym podejściem może być wczytanie skryptu normalnie, a następnie sprawdzenie, czy się wczytał i dociągnięcie stylów:

( async function() {
	const isReducedMotion = window.matchMedia( '( prefers-reduced-motion )' ).matches;

	if ( isReducedMotion || !( 'AOS' in window ) ) {
		return;
	}

	const stylesheet = document.createElement( 'link' );

	stylesheet.addEventListener( 'load', AOS.init );
	stylesheet.href = 'https://unpkg.com/aos@2.3.1/dist/aos.css';
	stylesheet.rel = 'stylesheet';
	
	document.head.append( stylesheet );
}() );

Jak już wspominałem, najlepiej by było, gdyby takie rzeczy robiła za nas biblioteka, ale często tak nie jest i musimy to dorabiać samemu. Od razu też lojalnie informuję, że nie analizowałem tego problemu od strony wydajności i nie wiem, jak szybkie są poszczególne sposoby. Skupiłem się przede wszystkim na aspektach użyteczności i dostępności. Najważniejsze jest zapewnienie, by animacje nie odpalały się u osób, które sobie tego nie życzą. Zatem minimalistyczne podejście mogłoby się sprowadzać do otoczenia AOS.init() w kod sprawdzający, czy user nie chce ograniczenia ruchu:

if ( !window.matchMedia( '( prefers-reduced-motion )' ).matches ) {
	AOS.init();
}

Z punktu widzenia dostępności ten krótki dodatek w kodzie stanowi naprawdę sporą zmianę.

4 komentarze do “Wpadki i wypadki #13”

  1. Niestety klient zazwyczaj ma wyrąbane na a11y i interesuje go głównie wynik wydajności w PageSpeed i innych tego typu raportach. Warto byloby uzupełnić artykuł o analizę wpływu opisanych technik na wydajność. Niestety, a11y nie jest priorytetem i proponowane rozwiązania, żeby w realnym świecie mogły być używane, muszą nie wpływać na wydajność. Stwierdzenie, że „nie sprawdzałem wpływu na wydajność” skreśla ten artykuł w praktycznym zastosowaniu w realnych stronach www, np. wizytówkach firmowych. Piszę to jako osoba, która bardzo lubi (w miarę moich ograniczonych umiejętności full-stackowych) dbać o a11y, na ile to możliwe.

    1. Akurat na PageSpeed i Lighthouse’a to nie powinno wpłynąć lub wpłynie pozytywnie. Z prostej przyczyny: CSS jest wczytywany w sposób nieblokujący, podobnie jak skrypt. Bardziej chodziło mi tutaj o odczucia użytkownika, czyli czy wczytanie tego nieblokująco nie spowoduje jakichś problemów z FOUC. Ale IMO też nie powinno, bo AOS jest inteligentny na tyle, żeby pokazywać elementy już widoczne w viewporcie.

  2. Jak zwykle artykuł bardzo przydatny. Dziękuję.

    PS. czy można korzystać z Twojego kodu/rozwiązania? Trzeba załaczać jakąś licencję?

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.