Animowanie rozmycia

Zamazywanie to świetny sposób na przekierowanie uwagi użytkownika. Niektóre elementy wizualne mogą wydawać się rozmyte, a inne pozostają w naturalny sposób utrzymywane w naturalny sposób przyciągania uwagi użytkownika. Użytkownicy ignorują zamazane treści i skupiają się na treści, którą mogą przeczytać. Może to być na przykład lista ikon, które po najechaniu na nie kursorem wyświetlają szczegółowe informacje o poszczególnych elementach. W tym czasie pozostałe opcje mogą być zamazane, aby przekierowywać użytkownika do nowo wyświetlonych informacji.

TL;DR

Nie można animować rozmycia, ponieważ jest to bardzo wolne. Zamiast tego oblicz z góry serię coraz bardziej rozmytych wersji i przechodź między nimi. Mój współpracownik, Yi Gu, stworzył bibliotekę, w której zajmie się wszystkim za Ciebie. Zachęcam do zapoznania się z naszą prezentacją.

Ta technika może być jednak dość rażąca, gdy jest stosowana bez okresu przejściowego. Dobrym pomysłem może być animowanie rozmycia – przejście z nierozmytego na rozmyte. Jeśli jednak zdarzyło Ci się to robić na stronie internetowej, to pewnie okazało się, że animacje nie są płynne, ponieważ w tym prezentacji widać, że nie masz potężnego urządzenia. Czy możemy działać lepiej?

Problem

Znacznik jest zamieniany przez procesor w tekstury. Tekstury są przesyłane do GPU. Procesor graficzny rysuje tekstury do bufora ramki za pomocą programów do cieniowania. Rozmycie odbywa się
w cieniowaniu.

Obecnie nie możemy sprawnie wykonywać animacji rozmycia. Możemy jednak znaleźć obejście, które wygląda dobrze, ale z technicznego punktu widzenia nie jest animowane. Na początek sprawdźmy, dlaczego animowane rozmycie działa wolno. Aby zamazać elementy w internecie, możesz skorzystać z 2 metod: właściwości CSS filter i filtrów SVG. Ze względu na zwiększoną obsługę i łatwość obsługi zwykle stosuje się filtry CSS. Niestety, jeśli musisz korzystać z przeglądarki Internet Explorer, nie możesz korzystać z filtrów SVG, ponieważ IE 10 i 11 obsługują je, ale nie CSS. Dobra wiadomość jest taka, że obejście animowania rozmycia działa w obu technikach. Spróbujmy znaleźć problem, korzystając z Narzędzi deweloperskich.

Jeśli włączysz opcję „Paint Flashing” w Narzędziach deweloperskich, nie zobaczysz żadnych błysków. Wygląda na to, że nie są one odświeżane. Technicznie jest to prawidłowe, bo „ponowne renderowanie” oznacza, że procesor musi ponownie malować teksturę promowanego elementu. Jeśli element jest zarówno promowany, jak i rozmyty, rozmycie jest stosowane przez GPU za pomocą programu do cieniowania.

Zarówno filtry SVG, jak i CSS używają filtrów konwolucyjnych do stosowania rozmycia. Filtry splotu są dość drogie, ponieważ w przypadku każdego piksela wyjściowego trzeba wziąć pod uwagę liczbę pikseli wejściowych. Im większy obraz lub większy promień rozmycia, tym droższy jest efekt.

I właśnie tu leży problem: każda klatka wymaga dość kosztownej operacji GPU, co powoduje zwiększenie budżetu wynoszącego 16 ms, co przekłada się na wynik znacznie poniżej 60 kl./s.

Przenieś się do królika

Co możemy zrobić, żeby wszystko działało bez problemu? Możemy wykorzystać w zasięgu ręki! Zamiast animować rzeczywistą wartość rozmycia (promień rozmycia), wstępnie obliczamy parę rozmytych kopii, w których wartość rozmycia rośnie wykładniczo, a następnie przenikamy się między nimi za pomocą funkcji opacity.

Przenikanie to seria nachodzących na siebie zanikań i zanikania. Jeśli np. mamy cztery etapy rozmycia, pierwszy etap zanika, a w drugim – zanikanie. Gdy drugi etap osiągnie 100%, a pierwszy osiągnie 0%, w fazie 3 zanikamy, a w trzecim zanikamy. Po zakończeniu tego etapu zanikamy w wersji czwartej i ostatniej. W tym scenariuszu każdy etap zajmie 1⁄4 całkowitego pożądanego czasu trwania. Pod względem wizualnym jest to bardzo podobne do prawdziwego, animowanego rozmycia.

W naszych eksperymentach najlepsze efekty wizualne przyniosły wykładnicze zwiększanie promienia rozmycia na etap. Przykład: jeśli mamy 4 etapy rozmycia, do każdej z nich przypiszemy parametr filter: blur(2^n), czyli etap 0: 1 piks., etap 1: 2 piksele, etap 2: 4 piksele i etap 3: 8 pikseli. Jeśli w zasadzie will-change: transform wymuszamy na każdej z tych zamazanych kopii na osobnej warstwie (zwanej „promowaniem”), przezroczystość tych elementów powinna zmieniać się bardzo szybko. Teoretycznie pozwoliłoby to nam przedstawić kosztowne zamazywanie. Logika okazuje się błędna. W tej wersji demonstracyjnej zobaczysz, że liczba klatek na sekundę jest mniejsza niż 60 kl./s, a rozmycie jest gorsze niż wcześniej.

Narzędzia deweloperskie pokazujące log czasu, w którym GPU ma długie okresy zajętych.

Szybki przegląd narzędzi deweloperskich wykazuje, że procesor graficzny jest nadal bardzo przeciążony i rozciąga każdą klatkę do ok. 90 ms. Ale dlaczego? Nie zmieniamy już wartości rozmycia, tylko przezroczystość, więc co się dzieje? Problem tkwi w charakterze efektu rozmycia. Jak wyjaśniliśmy wcześniej, jeśli element jest zarówno promowany, jak i rozmyty, efekt jest stosowany przez GPU. Mimo że nie animujemy wartości rozmycia, sama tekstura nadal jest nierozmyta i trzeba ponownie rozmyć każdą klatkę przez GPU. Powodem gorszej niż wcześniej liczby klatek jest fakt, że w porównaniu z naiwną implementacją GPU ma więcej pracy niż wcześniej, ponieważ w większości przypadków widoczne są 2 tekstury, które trzeba rozmyć niezależnie.

To, co nam przyszło do głowy, nie jest ładne, ale sprawia, że animacja jest niesamowicie szybka. Wracamy do tego, aby nie promować elementu do zamazania, a zamiast tego promujemy nadrzędny kod. Jeśli element jest zarówno rozmyty, jak i promowany, efekt jest stosowany przez GPU. To właśnie sprawiło, że nasza wersja demonstracyjna była powolna. Jeśli dany element jest rozmyty, ale nie jest awansowany, jest on zrastrowany do najbliższej tekstury nadrzędnej. W naszym przypadku jest to promowany nadrzędny element towarzyszący. Rozmyty obraz jest teraz teksturą elementu nadrzędnego i będzie można go użyć we wszystkich przyszłych klatkach. Jest to skuteczne, ponieważ wiemy, że rozmyte elementy nie są animowane, a ich zapisywanie w pamięci podręcznej jest korzystne. Oto prezentacja, w której zaimplementowano tę metodę. Ciekawe, co Moto G4 myśli o tym podejściu. Uwaga spojler: film jest świetny:

Narzędzia deweloperskie pokazujące śledzenie, gdzie GPU ma dużo czasu bezczynności.

Teraz mamy sporo miejsca na GPU i płynność 60 kl./s. Udało się!

Produkcja

W wersji demonstracyjnej wielokrotnie duplikowaliśmy strukturę DOM, aby mieć kopie treści do zamazania w różnych miejscach. Być może zastanawiasz się, jak to działa w środowisku produkcyjnym, ponieważ mogłoby to mieć niezamierzone skutki uboczne w przypadku stylów CSS autora, a nawet kodu JavaScriptu. Masz rację. Wejdź do Shadow DOM.

Chociaż większość osób uważa model Shadow DOM za sposób na przyłączenie elementów „wewnętrznych” do elementów niestandardowych, jest to jednak także podstawowa izolacja i wydajność. JavaScript i CSS nie przebijają granic DOM, co umożliwia nam powielanie treści bez ingerencji w style dewelopera czy logikę aplikacji. Mamy już element <div>, w którym każda kopia jest rastrowana, i teraz używamy tych elementów <div> jako hostów cieni. Tworzymy plik ShadowRoot za pomocą attachShadow({mode: 'closed'}) i dołączamy kopię treści do obiektu ShadowRoot zamiast do samego <div>. Musimy też kopiować wszystkie arkusze stylów do funkcji ShadowRoot, aby mieć pewność, że styl każdego kopii jest taki sam jak oryginał.

Niektóre przeglądarki nie obsługują modelu Shadow DOM w wersji 1. W przypadku innych stosujemy mechanizm powielania treści z nadzieją na to, że nic się nie psuje. Używamy metody polyfill DOM Shadow DOM z ShadyCSS, ale nie zaimplementowaliśmy tego w naszej bibliotece.

I o to chodzi. Po zakończeniu renderowania w Chrome opracowaliśmy skuteczne animowanie rozmycia w różnych przeglądarkach.

Podsumowanie

Nie należy używać tego efektu pochopnie. Kopiujemy elementy DOM i umieszczamy je w osobnej warstwie, dzięki czemu możemy przesuwać granice słabszych urządzeń. Kopiowanie wszystkich arkuszy stylów do każdego elementu typu ShadowRoot stanowi również potencjalne ryzyko dlatego, więc zastanów się, czy lepiej dostosować logikę i style, aby kopie w metodzie LightDOM nie miały wpływu na ich kopie, czy też skorzystać z naszej metody ShadowDOM. Czasami jednak warto zainwestować w naszą technikę. Jeśli masz jakieś pytania, zerknij na kod w naszym repozytorium GitHub i w prezentacji i daj mi znać na Twitterze.