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

Утро 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="http://thecode.local/wp-content/uploads/2019/12/snowfall2020.js">

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

var js = document.createElement('script'); js.src = "http://thecode.local/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 или най­ди­те кон­соль в меню браузера.

Результат

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