Чёртовы психи: как добавить снежинок на любой сайт одной строкой

Ну ладно, тремя строками. Это, считай, одна.

Чёртовы психи: как добавить снежинок на любой сайт одной строкой

Утро 30 декабря, редакционная летучка. Все помятые, уставшие, Данька немного похмельный. Нужно решить, что постить в остаток года.

— Давайте про C# и Си-подобные языки?
— Слишком сложно, всем пофигу.
— Тогда математическую задачу про скорость Санта-Клауса.
— Католическое рождество прошло, и всем пофигу.
— Чего люди сегодня хотят больше всего?
— Чтобы от них отвалили.
— …
— Чтобы снежок выпал. Но чтобы город не встал при этом.

Решено! В конце статьи — раздел «Готовый код», можно сразу скопировать, вставить и наслаждаться. Или читайте, как это сделано.

Идея и требования

Сегодня будем делать штуку с такими параметрами:

  • Запускает на веб-странице снегопад. Белые снежинки падают сверху вниз, не мешая основной странице.
  • Работает на любой странице, никаких дополнительных зависимостей.
  • Если я владелец сайта, то должен иметь возможность включить её буквально одной строкой, не программируя ничего и специально не отлаживая.
  • Если я гость, то я могу подключить её через консоль.
  • Никому не хочется сидеть над этим долго, поэтому сделано должно быть ЗА ПОЛЧАСА МАКСИМУМ.

Шаг 1. Своровать скрипт снежинок

Забиваем в поиске Snowfall webpage javascript и тыкаем буквально в первую ссылку: codepen.io/html5andblog/pen/pjKvgG — это скрипт Snow Flurry 2.0, автор S.W. Clough, лицензия MIT. Это значит, что этот скрипт можно использовать и в хвост, и в гриву, но указывать на эту лицензию. Вот, указали.

Сам код просто копипастим в новый файлик и называем его snowfall2020.js:

/*
snowFlurry JS - version 2.0
Copyright В© 2015 S.W. Clough (https://www.html5andbeyond.com)
Licensed Under MIT
*/
(function ($) {
  $.fn.snowFlurry = function (options) {
    var s = $.extend({
      maxSize: 5,
      numberOfFlakes: 25,
      minSpeed: 10,
      maxSpeed: 15,
      color: '#fff',
      timeout: 0
    }, options);
    var windowWidth = $(window).innerWidth(),
      WidthArray = [],
      DelayArray = [],
      animateArray = [],
      flakeSize = [],
      snowInterval;
    if (s.maxSize <= 10) {
      for (var i = 1; i < s.maxSize; i++) {
        flakeSize.push(i);
      }
    } else {
      for (var i = 1; i < 10; i++) {
        flakeSize.push(i);
      }
    }
    for (var i = 0; i < windowWidth - 20; i++) {
      WidthArray.push(i);
    }
    for (var i = 0; i < s.numberOfFlakes; i++) {
      $('<div class="sf-snow-flake"></div>').appendTo('body');
    }
    for (var i = 0; i < 10; i++) {
      DelayArray.push(i);
    }
    for (var i = s.minSpeed; i < s.maxSpeed; i++) {
      animateArray.push(i);
    }
    function getRandomFlakeSize() {
      var item = flakeSize[Math.floor(Math.random() * flakeSize.length)];
      return item;
    }
    function getRandomPosition() {
      var item = WidthArray[Math.floor(Math.random() * WidthArray.length)];
      return item;
    }
    function getRandomDelay() {
      var item = DelayArray[Math.floor(Math.random() * DelayArray.length)];
      return item * 1000;
    }
    function getRandomAnimation() {
      var item = animateArray[Math.floor(Math.random() * animateArray.length)];
      return item * 1000;
    }
    $('.sf-snow-flake').each(function () {
      var elem = $(this);
      elem.attr('data-speed', getRandomAnimation());
      elem.attr('data-delay', getRandomDelay());
      var elemSpeed = elem.attr('data-speed'),
        elemDelay = elem.attr('data-delay');
      var flakeSize = getRandomFlakeSize();
      elem.css({
        'width': flakeSize,
        'height': flakeSize,
        'border-radius': flakeSize / 2,
        'background-color': s.color,
        'box-shadow': '0 0 2px 1px' + s.color
      })
      function activateAnim() {
        setTimeout(function () {
          elem.css('left', getRandomPosition());
          elem.addClass('sf-snow-anim');
          elem.css('transition', 'top ' + elemSpeed / 1000 + 's linear');
          setTimeout(function () {
            elem.css('transition', '');
            elem.removeClass('sf-snow-anim');
          }, elemSpeed);
        }, elemDelay);
      }
      if (device.mobile() || device.tablet() || Modernizr.touch || $('html').hasClass('no-csstransitions')) { } else if (device.desktop()) {
        activateAnim();
        snowInterval = setInterval(function () {
          activateAnim();
        }, +elemDelay + +elemSpeed);
      }
      if (s.timeout != 0) {
        setTimeout(function () {
          clearInterval(snowInterval);
          $('.sf-snow-flake').fadeOut(1500, function () {
            $(this).remove();
          })
        }, s.timeout * 1000);
      }
    });
  };
}(jQuery));
jQuery(document).ready(function ($) {
  $(document).snowFlurry({
    maxSize: 10,
    numberOfFlakes: 100,
    minSpeed: 10,
    maxSpeed: 20,
    color: '#fff',
    timeout: 0
  });
});

Если интересно поднастроить снегопад под свои эстетические запросы, можно поковыряться в последней функции — где numberOfFlakes, maxSize, maxSpeed и color. Можно вместо снежинок сделать капли крови, установив бордовый цвет (#c90e0e); можно лёгкий снегопад превратить в метель, повысив numberOfFlakes до 800, а maxSpeed до 80–100. Но помните, что чем больше снежинок — тем больше нагрузка на процессор.

Шаг 2. Связанная библиотека

Snow-flurry.js требует библиотеки jQuery (мы о ней как-то писали). Несмотря на то, что эта библиотека очень распространена, по условиям задачи нам нужен полностью самостоятельный скрипт. Тут два пути.

Первый путь — умный: заставлять веб-мастера или пользователя сначала подключать jQuery, а потом уже подключать наш скрипт со снегопадом. Ну и оценивать, есть на странице jQuery или нет. Это увеличит объём кода, который нужно вставлять пользователю, и это не соответствует нашей задаче.

Второй путь — варварский: воткнуть код jQuery прямо внутрь нашего файлика snowfall2020.js. Минус этого подхода — у пользователя будет дважды загружаться jQuery, это плохо с точки зрения ресурсов. Но так как это новогодняя дурка, раз в год можно.

Находим нужную версию jQuery и варварски копипастим прямо в наш файлик. Слабонервным лучше не видеть, а бывалые и так представят.

Шаг 3. Инъекция CSS

Наша библиотека Snow Flurry требует нескольких строк CSS, чтобы снежинки правильно рисовались на экране. Сам CSS выглядит так:

body {
  margin: 0;
  background-color: #111;
}
.sf-snow-flake {
  position: fixed;
  top: -20px;
  z-index: 99999;
}
.sf-snow-anim {
  top: 110%;
}

Всё бы ничего, но мы должны подключать один файлик JS. Заставлять пользователя вручную подсовывать CSS в страницу мы не можем. Нам нужно вживить CSS в страницу с помощью JavaScript.

Проводим в поиске пять минут по ключевым словам Add CSS with JavaScript. Получаем такое заклинание:

var style = document.createElement('style');
style.innerHTML = 'YOUR CSS HERE';

document.head.appendChild(style);

Ахалай махалай, сим салабим, рахат лукум:

var style = document.createElement('style');
style.innerHTML = `
  .sf-snow-flake {
    position: fixed;
    top: -20px;
    z-index: 99999;
  }
  .sf-snow-anim {
    top: 110%;
  }
  `;

document.head.appendChild(style);

Этот нехитрый гибрид засунет необходимый CSS в начало нашего документа, дополнив таким образом таблицу стилей.

Добавляем это заклинание в начало нашего файлика.

Готовый код

На выходе получаем файл, который делает три вещи:

  1. Добавляет в страницу снегопадовый CSS.
  2. Загружает jQuery.
  3. Загружает и включает снегопад.

Вот полный код:

// Тут подключить JQuery
var style = document.createElement('style');
style.innerHTML = `
  .sf-snow-flake {
position: fixed;
top: -20px;
z-index: 99999;
}
.sf-snow-anim {
top: 110%;
}
  `;
document.head.appendChild(style);

/*
Copyright (c) 2014 Matthew Hudson - MIT License
device.js 0.1.61
*/
(function () { var a, b, c, d, e, f, g, h, i, j; a = window.device, window.device = {}, c = window.document.documentElement, j = window.navigator.userAgent.toLowerCase(), device.ios = function () { return device.iphone() || device.ipod() || device.ipad() }, device.iphone = function () { return d("iphone") }, device.ipod = function () { return d("ipod") }, device.ipad = function () { return d("ipad") }, device.android = function () { return d("android") }, device.androidPhone = function () { return device.android() && d("mobile") }, device.androidTablet = function () { return device.android() && !d("mobile") }, device.blackberry = function () { return d("blackberry") || d("bb10") || d("rim") }, device.blackberryPhone = function () { return device.blackberry() && !d("tablet") }, device.blackberryTablet = function () { return device.blackberry() && d("tablet") }, device.windows = function () { return d("windows") }, device.windowsPhone = function () { return device.windows() && d("phone") }, device.windowsTablet = function () { return device.windows() && d("touch") && !device.windowsPhone() }, device.fxos = function () { return (d("(mobile;") || d("(tablet;")) && d("; rv:") }, device.fxosPhone = function () { return device.fxos() && d("mobile") }, device.fxosTablet = function () { return device.fxos() && d("tablet") }, device.meego = function () { return d("meego") }, device.cordova = function () { return window.cordova && "file:" === location.protocol }, device.nodeWebkit = function () { return "object" == typeof window.process }, device.mobile = function () { return device.androidPhone() || device.iphone() || device.ipod() || device.windowsPhone() || device.blackberryPhone() || device.fxosPhone() || device.meego() }, device.tablet = function () { return device.ipad() || device.androidTablet() || device.blackberryTablet() || device.windowsTablet() || device.fxosTablet() }, device.desktop = function () { return !device.tablet() && !device.mobile() }, device.portrait = function () { return window.innerHeight / window.innerWidth > 1 }, device.landscape = function () { return window.innerHeight / window.innerWidth < 1 }, device.noConflict = function () { return window.device = a, this }, d = function (a) { return -1 !== j.indexOf(a) }, f = function (a) { var b; return b = new RegExp(a, "i"), c.className.match(b) }, b = function (a) { return f(a) ? void 0 : c.className += " " + a }, h = function (a) { return f(a) ? c.className = c.className.replace(a, "") : void 0 }, device.ios() ? device.ipad() ? b("ios ipad tablet") : device.iphone() ? b("ios iphone mobile") : device.ipod() && b("ios ipod mobile") : b(device.android() ? device.androidTablet() ? "android tablet" : "android mobile" : device.blackberry() ? device.blackberryTablet() ? "blackberry tablet" : "blackberry mobile" : device.windows() ? device.windowsTablet() ? "windows tablet" : device.windowsPhone() ? "windows mobile" : "desktop" : device.fxos() ? device.fxosTablet() ? "fxos tablet" : "fxos mobile" : device.meego() ? "meego mobile" : device.nodeWebkit() ? "node-webkit" : "desktop"), device.cordova() && b("cordova"), e = function () { return device.landscape() ? (h("portrait"), b("landscape")) : (h("landscape"), b("portrait")) }, i = "onorientationchange" in window, g = i ? "orientationchange" : "resize", window.addEventListener ? window.addEventListener(g, e, !1) : window.attachEvent ? window.attachEvent(g, e) : window[g] = e, e() }).call(this);
/*
snowFlurry JS - version 2.0
Copyright В© 2015 S.W. Clough (https://www.html5andbeyond.com)
Licensed Under MIT
*/
(function ($) {
  $.fn.snowFlurry = function (options) {
    var s = $.extend({
      maxSize: 5,
      numberOfFlakes: 25,
      minSpeed: 10,
      maxSpeed: 15,
      color: '#fff',
      timeout: 0
    }, options);
    var windowWidth = $(window).innerWidth(),
      WidthArray = [],
      DelayArray = [],
      animateArray = [],
      flakeSize = [],
      snowInterval;
    if (s.maxSize <= 10) {
      for (var i = 1; i < s.maxSize; i++) {
        flakeSize.push(i);
      }
    } else {
      for (var i = 1; i < 10; i++) {
        flakeSize.push(i);
      }
    }
    for (var i = 0; i < windowWidth - 20; i++) {
      WidthArray.push(i);
    }
    for (var i = 0; i < s.numberOfFlakes; i++) {
      $('<div class="sf-snow-flake"></div>').appendTo('body');
    }
    for (var i = 0; i < 10; i++) {
      DelayArray.push(i);
    }
    for (var i = s.minSpeed; i < s.maxSpeed; i++) {
      animateArray.push(i);
    }
    function getRandomFlakeSize() {
      var item = flakeSize[Math.floor(Math.random() * flakeSize.length)];
      return item;
    }
    function getRandomPosition() {
      var item = WidthArray[Math.floor(Math.random() * WidthArray.length)];
      return item;
    }
    function getRandomDelay() {
      var item = DelayArray[Math.floor(Math.random() * DelayArray.length)];
      return item * 1000;
    }
    function getRandomAnimation() {
      var item = animateArray[Math.floor(Math.random() * animateArray.length)];
      return item * 1000;
    }
    $('.sf-snow-flake').each(function () {
      var elem = $(this);
      elem.attr('data-speed', getRandomAnimation());
      elem.attr('data-delay', getRandomDelay());
      var elemSpeed = elem.attr('data-speed'),
        elemDelay = elem.attr('data-delay');
      var flakeSize = getRandomFlakeSize();
      elem.css({
        'width': flakeSize,
        'height': flakeSize,
        'border-radius': flakeSize / 2,
        'background-color': s.color,
        'box-shadow': '0 0 2px 1px' + s.color
      })
      function activateAnim() {
        setTimeout(function () {
          elem.css('left', getRandomPosition());
          elem.addClass('sf-snow-anim');
          elem.css('transition', 'top ' + elemSpeed / 1000 + 's linear');
          setTimeout(function () {
            elem.css('transition', '');
            elem.removeClass('sf-snow-anim');
          }, elemSpeed);
        }, elemDelay);
      }
      if (device.mobile() || device.tablet() || Modernizr.touch || $('html').hasClass('no-csstransitions')) { } else if (device.desktop()) {
        activateAnim();
        snowInterval = setInterval(function () {
          activateAnim();
        }, +elemDelay + +elemSpeed);
      }
      if (s.timeout != 0) {
        setTimeout(function () {
          clearInterval(snowInterval);
          $('.sf-snow-flake').fadeOut(1500, function () {
            $(this).remove();
          })
        }, s.timeout * 1000);
      }
    });
  };
}(jQuery));
jQuery(document).ready(function ($) {
  $(document).snowFlurry({
    maxSize: 10,
    numberOfFlakes: 100,
    minSpeed: 10,
    maxSpeed: 20,
    color: '#fff',
    timeout: 0
  });
});

Как использовать: если у вас HTTPS

Посмотрите на иконку вашего сайта или адрес. Если там стоит замочек или написано HTTPS, вам нужны скрипты, переданные по безопасному протоколу. Если нет замочка, написано HTTP или «Небезопасно» — вам в следующий раздел.

Для вебмастеров — добавьте на страницу такой код:

<script src="https://thecode.media/wp-content/uploads/2019/12/snowfall2020.js">

Для гостей сайта — откройте консоль и вставьте такой код:

var js = document.createElement('script'); js.src = "https://thecode.media/wp-content/uploads/2019/12/snowfall2020.js"; document.body.appendChild(js);

Чтобы открыть консоль: CMD + ALT + I; Ctrl + Alt + I или найдите консоль в меню браузера.

Как использовать: если у вас HTTP

Для сайтов без замочка и с протоколом HTTP нужен скрипт, размещённый на незащищённых сайтах.

Для вебмастеров — добавьте на страницу такой код:

<script src="http://mihailmaximov.ru/projects/snow/snowfall2020.js">

Для гостей сайта — откройте консоль и вставьте такой код:

var js = document.createElement('script'); js.src = "http://mihailmaximov.ru/projects/snow/snowfall2020.js"; document.body.appendChild(js);

Чтобы открыть консоль: CMD + ALT + I; Ctrl + Alt + I или найдите консоль в меню браузера.

Результат

Лучше всего выглядит на сайтах с тёмным или цветным фоном, потому что белый снег на белом фоне не виден. А так — красота:

Чёртовы психи: как добавить снежинок на любой сайт одной строкой

Часто задаваемые вопросы

Как сделать снежинки полупрозрачными или изменить их форму?

Есть два варианта: через настройки скрипта или через CSS. Разберёмся, что и где менять.

1. Цвет и количество (лезем в скрипт)

Базовые параметры лучше менять прямо в коде вызова скрипта. Найдите внизу файла snowfall2020.js или там, где вы его подключаете, этот блок:

$(document).snowFlurry({
 // Размер
 maxSize: 10,
 // Количество (не ставьте больше 500, браузер зависнет)          
 numberOfFlakes: 100,
 // Цвет (можно написать 'red' для кровавого снега) 
 color: '#fff',        
});

Меняйте эти параметры смело — это самый правильный способ.

2. Прозрачность и форма (лезем в стили)

Наш скрипт создаёт стандартные кружочки, и, если вы хотите сделать их полупрозрачными или квадратными, добавьте эти стили в ваш CSS-файл. 

Мы обращаемся к классу .sf-snow-flake:

/* Прозрачность */
.sf-snow-flake {
   opacity: 0.7; /* 0.5 — полупрозрачные, 1 — как молоко */
}

По умолчанию скрипт скругляет углы через border-radius. Если вы хотите квадратные снежинки в стиле Minecraft, то нужно отменить скругление:

.sf-snow-flake {
   border-radius: 0 !important; /* !important нужен, чтобы перебить настройки скрипта */
}

А ещё вы можете заменить кружочки на реальные изображения снежинок, или логотип вашей любимой команды, или котиков. Для этого нужно через CSS перекрыть то, что рисует скрипт, и наложить свой фон.

.sf-snow-flake {
   /* Ставим свою картинку */
   background: url('https://example.com/snow.png') no-repeat center center !important;
   /* Чтобы картинка влезла в размер */
   background-size: contain !important;
  
   /* Убираем рисованные эффекты скрипта */
   /* Убираем круг */
   border-radius: 0 !important;
   /* Убираем свечение вокруг */
   box-shadow: none !important;
  
   /* Делаем исходный цвет прозрачным, чтобы не было цветного фона под картинкой */
   background-color: transparent !important;
}

Обратите внимание: слово !important здесь обязательно, так как скрипт прописывает стили прямо в HTML и нам нужно их принудительно переопределить.

Можно ли запустить снегопад только в шапке сайта или в определённом блоке, а не на всей странице?

Да, но для этого нужно объяснить скрипту, куда именно сыпать снег, а стилям — где его обрезать.

Шаг 1. Правка скрипта: выбираем контейнер для снега

Найдите в файле snowfall2020.js строчки 34–36. Они отвечают за создание и размещение снежинок на странице:

for (var i = 0; i < s.numberOfFlakes; i++) { 
   // Создаём div и прикрепляем его к body, то есть ко всей странице
   $('<div class="sf-snow-flake"></div>').appendTo('body');
}

Вам нужно заменить исходный 'body' на класс или ID вашего блока. Например, если у вашего хедера класс .header, код будет таким:

// Теперь снежинки будут внутри хедера
.appendTo('.header'); 

Шаг 2. Правка CSS: создаём границы

По умолчанию снежинки могут улетать за пределы своего родителя. Чтобы запереть их внутри хедера или любого другого блока, добавьте эти стили:

.header {
   /* Делаем блок точкой отсчёта  */
   position: relative;
  
   /* Обрезаем всё, что выпадает за границы блока */
   overflow: hidden;  
}

/* Меняем позиционирование самих снежинок */
.sf-snow-flake {
 /* Вместо fixed, чтобы они слушались родителя */
   position: absolute;
}

Логика такая, что position: relative у родителя создаёт «песочницу». Теперь координаты top: 0 для снежинки будут означать не «верх экрана», а «верх хедера». А свойство overflow: hidden работает как ножницы: если снежинка пролетит ниже границы хедера, она визуально исчезнет, а не поползёт по контенту сайта.

Как добавить анимацию вращения или «покачивания» снежинок при падении?

Реальные снежинки не падают вниз по прямой — они вращаются, колеблются из стороны в сторону. Чтобы создать на странице такой же красивый и реалистичный эффект, лучше всего использовать для этого CSS-анимацию. 

Откройте тот же файл или блок <style>, куда вы вставляли код из «Шага 3» основной статьи (там, где body и .sf-snow-flake), и добавьте туда этот блок:

/* Создаём сценарий анимации с именем "spin" */
@keyframes spin {
   from {
       /* Начальная точка: 0 градусов */
       transform: rotate(0deg);
   }
   to {
       /* Конечная точка: полный оборот 360 градусов */
       transform: rotate(360deg);
   }
}

/* Применяем этот сценарий к нашим снежинкам */
.sf-snow-flake {
   /* Название анимации | Длительность | Характер движения | Бесконечно */
   animation: spin 3s linear infinite;
}

Так мы говорим браузеру: «Бери каждую снежинку и вращай её вокруг своей оси. Делай один полный оборот за 3 секунды, равномерно и бесконечно». 

Теперь снег будет выглядеть намного живее!

А если хотите лучше разобраться, как работают keyframes и создавать более сложные движения, например покачивание влево-вправо, то загляните в нашу статью «CSS-анимации: большой разбор»

Как полностью убрать снегопад со страницы?

Если пришло время завязывать со снегопадом или он вам просто надоел, то самый надёжный способ — просто скрывать сами элементы снежинок. Поскольку скрипт использует jQuery, сделать переключатель будет очень просто.

Шаг 1. Создаём кнопку

Вставьте этот код в HTML-файл вашей страницы, в то место, где вы хотите видеть кнопку, например в футер или в хедер:

<button id="toggle-snow">Вкл/Выкл ❄️</button>

Шаг 2. Оживляем кнопку

Добавьте этот код в самый конец вашего файла snowfall2020.js или в любой другой скрипт на странице после подключения jQuery:

// Ждём, пока страница загрузится
jQuery(document).ready(function ($) {
   // При клике на кнопку с id="toggle-snow"
   $('#toggle-snow').on('click', function() {
       // Находим все снежинки и переключаем их видимость
       $('.sf-snow-flake').toggle();
   });
});

Если вы хотите не просто скрыть, а полностью удалить снег, чтобы разгрузить процессор, замените строчку $('.sf-snow-flake').toggle(); на:

// Удалит снежинки насовсем
$('.sf-snow-flake').remove(); 
// И скроет саму кнопку, так как она больше не нужна
$(this).hide(); 

Обложка:

Даня Берковский

Корректор:

Ирина Михеева

Вёрстка:

Маша Климентьева

Вам может быть интересно
medium