Анимация падающего снега на Canvas эффективнее анимации на DOM в несколько раз
В сравнении с нативным JS на элементах DOM, реализация анимационных алгоритмов на Canvas обычно производительнее во много раз. Это известный факт (но с особенностями для малого числа частиц, как выяснится позже), и он может найти реализацию так всем мешающего традиционного под НГ, но гонимого рациональными пользователями «падающего снега». Чтобы нагрузки было мало, в последние годы считается хорошим тоном «запускать» снег на сайте едва заметным, с минимальным количеством снежинок (5-15). Тут и эффект есть, и нагрузки на процессор почти никакой.
Поэтому, пока до НГ ещё несколько дней ещё зима, предлагаю устроить хакатончик по реализации лучших алгоритмов на канвасе и их аналогов на DOM, взяв за основу в основном древние нативные алгоритмы, которые как максимум обёртывались в плагин jQuery, чтобы было удобно подключать. Большая часть этих алгоритмов не соразмеряет нагрузку на процессор или сделана неэффективно, поэтому даже при малом числе снежинок грузят процессор на 100%. Вот пример обзорной статьи, где рассмотрены более 10 реализаций, не все, встречающиеся в природе. В дополнение, рассмотрим несколько избранных, чтобы получить задел на развитие алгоритма и реализацию его с хорошей эффективностью (получится ещё 5-6 вариантов). На этой основе можно построить доработку. Github с 12 демо (ссылки повторяются ниже) и несколькими алгоритмами. Как обычно, жизнь статьи на Хабре не длится более 1.5 дней, поэтому нельзя рассчитывать акцию эффективной. Но после Нового Года случаются разные чудеса: поток посетителей падает, и случайные лунатики получают шанс не забыть одну статью целых 5 дней. На них и делается ставка.
Лучше если она будет привязана к длительному процессу, например, к клубу Анонимных Дедов Морозов, заходя в который всегда можно будет получить привязку к обсуждению и развитию вариантов «падения снега» и при этом он не привязан жёстко к празднику. Результат в настоящее время — здесь: geekadm.ru/#/2015, и есть, конечно, промежуточные эксперименты типа 46.101.141.171/snow.html?v=4 — снег для HabraAdm с дополнительными эффектами.
Предварительный «хакатон» был устроен парой участников и (или) посетителей клуба — (1), (2). В результате, получилось до 10 вариантов на движке с Canvas. Их число может быть намного больше, но на всё нужно свободное время, поэтому акция превратилась в открытую. Например, можно «прикрутить» к движку уже написанную в 2003 году модель падения снежинок автором Peter Gehrig. Доработка в 2005-2006 показывает на современных браузерах, что 20-30 снежинок незначительно нагружает процессор (10-20%, зависит от мощности и видеокарты; здесь и далее будем говорить о загрузке одного ядра процессора, т.к. JS — однопоточный), а по свидетельствам старых форумов, подобное было и на старых браузерах типа Opera 9 и IE6, но несколько хуже. На Canvas с тем же алгоритмом можно было бы запускать раз в 5 больше объектов.
Заглянем в историю и посмотрим на её ход
Давайте начнём с начала, с 2002-2005 годов примерно. Джаваскрипт показал способность анимировать падение снежинок. Правда, оказалось, и это было естественным, анимация на скрипте или непроизводительная, или сильно грузит процессор. Поэтому и в те старые времена нужно было или очень экономить ресурсы, или грузить процессор по максимуму, на 100% в потоке браузера, (на 1 ядре) что тормозило другие вкладки (в тех браузерах типа Оперы 9, где они были) и окна, и это вызывало плохо скрываемое негодование посетителей таких страниц.
Через лет 8 нашлось решение этой проблемы на уровне браузера. Основные браузеры, начиная с Хрома, стали приостанавливать выполнение скриптов на невидимых вкладках. Это помогло не мешать просмотру других окон и программ (но не приостанавливается при переходе на другое окно, а только уменьшается нагрузка, если окно не отрисовывается на экране или видно частично). Правда, в связи с мобильной эрой, открывание страниц с грузящей анимацией, всё равно, не воспринимается положительно.
Но за эти годы появился и более совершенный механизм анимации — Canvas. Сразу надо заметить, что другой новаторский механизм — анимации в CSS — не помогают экономить нагрузку, и тратят энергию процессора ровно столько же, как и анимация на Javascript + DOM. То есть, если искать лучшее решение, то приходится делать это в Canvas.
Canvas, как это будет видно в примерах, позволяет сотню снежинок запустить с нагрузкой 30-40% для среднего настольного компьютера или ноутбука. Это примерно в 5 раз эффективнее, чем умеют лучшие оптимизированные алгоритмы на JS+DOM.
Как дань истории, рассмотрим и «классические» алгоритмы, которые ждут своего часа для оптимизации и перевода на Canvas.
Очень много алгоритмов легко найти поиском в Github по словам типа «snowfall js», «snow flakes js». Они дополнят хорошую подборку из 12 скриптов с демо-ссылками, упомянутую выше (или расширенная копия).
Обзор алгоритмов с примерами
(При просмотре страниц с примерами будьте готовы к иногда 100%-ной загрузке ядра процессора. При показе ссылки в статье об уровне загрузки предупреждается.)
Многие алгоритмы, кроме того, что неэффективны, ещё и не похожи по поведению на реальное поведение снежинок. Наверное, многим помнится бросающееся в глаза несоответствие реальности движение снежинок по синусоиде. Однажды увиденное (или новее), оно запоминается надолго в качестве отрицательного примера. Поэтому, задача алгоритма — быть не только эффективным, но и отражающим реальность. (По эффективности, вычисление гармонических функций тоже должно проигрывать.)
Из ранее пересмотренных алгоритмов наиболее близкими к реальности можно считать два — со случайными перемещениями (варьировать надо скорости, а не координаты) и упомянутый алгоритм Peter Gehrig (2003), в котором движение управляется 4 переменными, и одна из них — гармонически изменяющаяся, что создаёт периодические эффекты «завихрений». Впрочем, по этому поводу можно спорить и добавлять в копилку новые реализации.
Из найденных реализаций алгоритма P.Gehrig нашлась страничка с примером: www.softtime.ru/javascript/snow-falling.html. По ней понятно, о чём идёт речь. Небольшое количество снежинок (15-20) и сейчас не сильно грузит процессоры (10-20% для не слабых ноутов), работает в Хроме и Firefox, хотя в списке поддержки читается Opera 9, IE5 и 6 и какой-то ранний Firefox. Разумеется, о Canvas тогда не знали.
Сведём увиденное в таблицу, где покажем: *) количество частиц; *) тип анимации; *) наличие песочницы для экспериментов; *) загрузка процессора (MacBook, Firefox 43) (Хром обычно % на 10 эффективнее); *) комментарии об особенностях.
(Реальные данные загрузки процессора будут отличаться от указанных для разных компьютеров, для компьютеров с разными видеокартами и для мобильных устройств в широких пределах. Даны для ориентировки для среднемощного ноутбука со встроенной графикой.)
- Упакованный (обфусцированный) оригинальный код в песочнице с адаптацией к jQuery 2.
- Распакованный, но плохо движущийся (малые синусоиды) мод mod1 кода в песочнице с адаптацией к jQuery 2.
- Восстановленный с рефакторингом имён, стиля, максимально похожий и работающий код (Fiddle) этого скрипта в песочнице. Останавливается/запускается по кнопке останова. В сжатом виде — 1.5К.
- Расширенный до возможности запуска нескольких объектов в блоках или в целом окне. Запуск по коллекции. В сжатом виде — 1.8 К, меньше оригинального, который 2.2-2.4 для разных модов.
Далее стало интереснее — в коде при тестируемых настройках обнаружился ряд багов (и это не значит, что нашлись все). Кое-какие правки в коде делать придётся (убрать несовместимость с jQ 2+, вычисление начального положения снежинки, не давать заходить им за левый край окна — в маках это приводит к «вспыхиванию» полосы прокрутки, и полезно то же самое делать для нижней границы), поэтому пользоваться лучше модифицированными современными версиями.
Версии-моды (2009-2010) добавили пару насущных «фич» — настройки плавного исчезания снежинок внизу (fadeAway: 1) и возможность прокрутки с неперемещением поля снежинок (followScroll: 1). Названия оставлены оригинальными, какими назвали их разработчики.
Цель отладки старого кода в том, чтобы потом алгоритм перенести на Canvas и сравнить скорости. (Калек побеждать — чести маловато.) В настройках я повторяю те же ошибки, над которыми критиковал других. Для разработчика неважно, что разноцветный снег, падающий в 2D, не бывает — главное, есть техническая возможность раскрасить. А ещё, летят не снежинки, а комки в реальности. Но 3/4 населения Земли этого не знает, так что нас ждёт успех… В наборе снежинок использовано большинство символов Unicode, похожих на снежинки — набралось аж 8. Можно найти ещё столько же.
Итак, получился читаемый код, на 20 строчек короче (90 строк) и в упакованном виде на 30% меньше (1.5К), чем лучшие образцы модов, не потеряв при этом функций и добавив стоп-старт-кнопку. Этот промежуточный вариант компактен, но работает, как и раньше, с окном и абсолютно может задавать высоту (height: число) в параметрах. Это не очень удобно, тем более, что плагин не использует контекстную коллекцию вообще, поэтому нормально себя чувствует с вызовом странного вида $().jSnow(. ), т.е. без контекста. Получилось довольно просто, ограничимся здесь ссылкой на демо.
И усложним задачу — начать работать с контекстными коллекциями jQuery. Сохраним это поведение, когда нет контекста — выбираем работу с окном, как и при контексте window и $('body')) и добавим работу с блоком-контейнером (например, запускать по $('.contSnow').jSnow(. ) ). Контейнер придётся подготовить, для него нужен position:relative, чтобы вставляемый блок с absolute хорошо себя чувствовал. Блок, который на всё окно, тоже начнёт себя вести по-другому, будет покрывать окно, поэтому весь контент страницы потребуется поднять, если будет такой блок. (Можно и иначе, по-старому, но зачем усложнять? Скорее, будут использовать или первый скрипт, или второй.) Получится примерно так:
В самом деле, зачем работать только с одним контейнером, если jQuery может предоставить коллекцию? Получается что-то типа такого, нагрузка 30-50% для суммы в 70-120 частиц. Остановку-запуск можно тоже разделить по кнопкам или сделать общей.
При попытках применить написанное сразу бросается в глаза непрактичность запуска скрипта на коллекции. Запустить — да, можно, даже десятки инстансов скриптов, раздавая каждому по чуть-чуть снежинок. Но всем блокам коллекции сообщаются одинаковые параметры, и это — непрактично, т.к. чаще хочется сделать другую задачу — запустить немного окон (виртуальных, на дивах), но с разными параметрами, а для этого приходится их сообщать одному элементу. Получается, чаще всего, что сколько элементов, столько нужно запусков .jSnow, что получается длинно, хотя и наглядно.
Тут возникает аналогия с написанием правил CSS. Правила — это те же декларации параметров, какие мы делаем при запусках плагинов, даже селекторы у плагинов стоят на своём месте. Но нет сейчас механизам постепенно накапливать описания правил (подобно тому, как в одном — font-size, в другом — position). Второй запуск плагина на том же элементе добавляет ещё один инстанс плагина (долгоиграющую функцию с замыканием и возможностью выключения), а не добавляет свойств к уже запущенному. При этом удобнее было бы перечислять селекторы (просто строки с классами), а не JSON-ы настроек. Но не припомнится ни одного плагина, где такой способ конструирования настроек был бы нужен и работал. На самом продвинутом варианте демо напишем запуск в 4 областях так, как плагин теперь умеет:
Доработки плагина добавили порядка 300 байт сжатого кода, но теперь он с бОльшим основанием может называться плагином. Правда, без единого метода.
Другие скрипты для работы с DOM
Такое внимание уделено одному скрипту, не самому продвинутому, потому что, если заглянуть вперёд, он оказывается небольшим конкурентом скриптам на Canvas, не тем, что выше, а хорошо, экономично написанным. Неэффективно написанные не станут конкурентами, поэтому в коде нельзя допустить ничего лишнего, чтобы было что с чем сравнивать.
Например, есть очень популярный скрипт на DOM под названием Snowstorm, со сжатым кодом 20К, очень большим количеством настроек, отключением в мобильных устройствах — есть, чему поучиться и для малых скриптов. Но изучать, что там лишнего и приводит ли это к потерям — слишком сложная задача. Проще написать малое и расширять настройки. Значимость каждой выяснится на тестах и практике.
Как там с CSS-анимациями?
Смотрим примеры, написанные другими, дальше, в поисках эффективных алгоритмов и эффектных результатов.
1. Пример (почти единственный с анимациями CSS) сильно загружающей анимации на CSS3 с вращением DOM-снежинок — www.jqueryscript.net/animation/jQuery-Plugin-For-Snowfall-Effect-with-Rotating-Snowflakes.html. (На этом сайте есть всё — описание, код и демо для каждого скрипта, но без песочниц.)
2. Анимация на 3 картиночных слоях в CSS (Codepen).
Пробуем смотреть ещё анимации на канвасе, чтобы найти не сильно нагружающий систему скрипт.
Пример анимации на Canvas, активирующейся по движению мыши, где можно увидеть, что приемлемое количество снежинок — не сплошная генерация с порядка 200-300, а порядка 50-100 на экране — www.jqueryscript.net/animation/Yet-Another-Falling-Snow-Animation-with-jQuery-Canvas-GlauserChristmas.html.
Все эти анимации, включая Canvas, почему-то настроены так, что грузят процессор на 100% (точнее, одно ядро, которое может загрузить однопоточный JS). Это будто бы считается нормальным для демонстраций скриптов. (Возможно, на дискретных видеокартах это так и есть.)
Как-то это всё странно
Среди скриптов со снегом встречается очень мало экономящих нагрузку процессора, хотя они есть как для DOM, так и для Canvas. Покажем, что того же эффекта полной загрузки можно добиться не для 100, а для пары тысяч снежинок, а нормальной лучше считать загрузку ядра на 10-20% для 200-400 штук.
Выбираем эффективный Canvas
Более детально, результаты такие. Берём jsFiddle как менее влияющий на результаты, копируем туда пример с добавлением кнопки остановки. Запускаем разное количество снежинок, ждём примерно минуту, пока расходы процессора успокаиваются (не двигая мышь, ничего не делая). Смотрим результаты через Activity Monitor (MacBook с видео Intel HD Graphics 5000). Если смотреть на Windows, скажем, на 4-ядерном процессоре, то увидим загрузку не более 25%. Называемые здесь в статье 100% относятся к загрузке одного ядра, т.е. 25% надо будет умножать на 4. Просто браузеры пока по-другому не умеют распараллеливаться, но и эта нагрузка для ноутбука достаточно чувствительна, приводит к расходу энергии, приблизительно как при просмотре видео.
Незапущенная отрисовка -10% нагрузки (фон). 0 частиц — 30%; 2 частиы — 35%; 25 частиц — 40%; 200 частиц — 50%; 1000 — 55%; 2000 — 65%. При этом не всегда показания повторяются, зависит от других запущенных программ и занятости памяти.
Вот и разгадка того, почему с появлением Canvas все сразу не бросились программировать на нём. Процессоры, начальное возможное несовершенство реализации и неполная поддержка в браузерах не позволяли делать приемлемый битрейт, чтобы с хорошим качеством движения пройти «ценз производительности», зависящий не от числа частиц, а от битрейта, и уложиться при этом в идеале в 20%. С анимацией на слоях можно запустить хотя бы 5 частиц и пройти этот ценз. Сейчас с производительностью стало лучше, а IE8, который без Canvas, занимает долю около 0.5 процента.
Свои алгоритмы на канвасе
Создадим свои варианты экономичных алгоритмов на канвасе. Сначала замкнём плоскость в тор и пустим снежинки по ветру с небольшими случайными блужданиями, которые применяются к скорости каждой частицы. Получается довольно естественно, хотя без впечатления о порывах ветра. Второй эффект — параллакс, когда более мелкие частицы падают медленнее, что создаёт ощущение трёхмерности сцены.
Вначале нужно запустить заряд снега помощнее, чтобы быстро образовался покров. Через минуту он на глазах начнёт таять, а полностью растаявшие частицв снизу переходят наверх. Круговорот воды.
Скрипты Kafeman в рамках хакатона
Параллельно скрипты со снегом и другими праздничными эффектами делал kafeman. Начальная идея — построить приветственную страницу с эффектами для посетителей клуба АДМ. Всё могло быть серьёзней, но история не терпит сослагательного наклонения. Кроме чисто декоративного оформления, посетила идея посмотреть на реальный снегопад (1, 2, 3) и смоделировать те неприятные близко мелькающие частицы снега. Совершенство ещё не достигнуто, но направление имеет все основания, иначе не вспомнится лепящийся на лицо отвлекающий снег. Тут надо поэкспериментировать с отрисовкой бесформенных хлопьев и их смазанным мельканием.
И надо как-то отращивать снежный покров: jsfiddle.net/162e4jte/3, но не так, чтобы он поглотил всю планету с космонавтом на ней. Поначалу он тоже ложился бугорками и таял: jsfiddle.net/162e4jte/1. Такое поведение встречалось в скриптах других авторов.
Накоец, на будущее идея объединения генераторов движков в один тоже заслуживает исполнения. По результатам, кроме массы скриптов сторонних авторов, имеем 3-4 алгоритма движения частиц и 2 способа реализации, общие части которых достаточно хорошо по представленным песочницам и репо отслеживаются.
Немного статистики. В связи с глобальным потеплением интерес к снегу в совокупности с Javascript, как и к лыжам — падает, а интерес к Canvas и Javascript — растёт. Теперь мы знаем, в какой комбинации будет обеспечен стабильный интерес.
Заключение
2 реально выполнимых для эффекта снега способа программирования — DOM-слои и Canvas — имеют свои ниши применения. До примерно 50 частиц выигрывает DOM, но не во всех режимах, для задач, где нужна малая нагрузка на процессор. Высокий битрейт понижает планку. Более 50 (но уверенно — более 200) — но тоже с желательно низким битрейтом — выигрывает Canvas. Но раскошеливаться надо на загрузку побольше — от 30% ресурсов процессора. Граница и критерии — очень зыбка и зависит от аппаратуры, браузера, успехов реализаций движка в браузерах. Мобильный пользователь скажет категорическое «нет» более 5 частицам, а стационарник с большой видеокартой искренне не будет понимать, в чём проблемы с 200 частицами в DOM и с 5000 в Canvas.
Всё лишнее из алгоритма для типичного коммерческого применения (внимание пользователя в районе праздников) нужно убирать, тестировать алгоритмы на разных устройствах и принимать решения о нише покрытия пользователей. Могут быть более выигрышная низкобитрейтная иллюминация на Canvas или кратковременные фейерверки на нём же. Не помешает подсказать, какой кнопкой выключить анимацию и запомнить в настройках юзера, что «акция отработала». Поскольку снег не такой денежный, как Яндекс-браузер, нет оснований «ломать» эту кнопку сознательно. Ещё через пару лет можно надеяться, что положение с канвасом улучшится, и он медленно будет отбирать себе область малого количества частиц и повышенного битрейта.
Выразительные возможности скриптов снега, в основном, на данном этапе исчерпаны (ничего качественно нового не появилось в последние годы в этой группе). У достаточно завершённых скриптов есть ряд свойств, признанных необходимыми, а часть невыполнимых по производительности свойств (вращение снежинок в формате 3D) не выполняется, но никого это не смущает. Но из-за производительной отрисовки на канвасе можно ожидать, что такие скрипты появятся (с нагрузкой от 30%).