Новогодний снежок на сайт

Один из наших клиентов попросил поставить новогоднюю заставку на сайт. Польза — рассказать клиентам, что до конца праздников они не работают и вернутся только с 11-го января. Ну а заодно, повеселить пользователей новогодним видом сайта. Для этого мы сделали гифку с новогодними игрушками и снегопадом.

Пока ставили все это на сайт стали обсуждать с коллегами, что прикольно было бы на весь сайт пустить снег. В интернете нашлись уже разные исполнения нашей задумки, но на боевой проект их бездумно ставить страшно. Во-первых, многие тормозят, ведь не у всех клиентов быстрый интернет и современные браузеры. Во-вторых, ставить чужие скрипты опасно в принципе.

В общем, не хочу больше затягивать, я за пол часика собрал наше «легкое» исполнение. Идею подсмотрел, но убрал все лишнее и сделал под себя. Хочу им поделится с читателями. Почитать будет полезно тем, кто только изучает джаваскрипт. А итог могут использовать на своих проектах все желающие. Так же буду рад критике коллег.

Результат

Процесс

Эта часть для тех, кто только изучает джаваскрипт. Всем остальным, думаю, будет не очень интересно.
Все, что нам понадобится в HTML, это canvas с идентефикатором snow. И я запустил скрипт по событию на BODY onload=“init()”. Это не лучшее решение, но оно за рамками самого «снега», так что оставлю его на совесть того, кто будет код использовать.
Весь наш код состоит из четырех функций.
function init() — тут мы делаем подготовку к запуску и запускаем. В этой функции должны быть все операции, которые нужно произвести до основного кода.
function draw() — отвечает за отрисовку текущего состояния снега.
function update() — отвечает за установку нового состояния. Оно будет отрисовано на следующем шаге.
function mousemove(event) — отвечает за отслеживание положения мыши. Эту функцию я добавил по тому, что захотелось какого-то интерактива. Снег не должен всегда падать только вниз, в жизни его сдувает ветром то туда, то сюда. Я решил, что за ветер будет отвечать курсор. Чем дальше он от центра экрана (горизонтално), тем сильнее ветер в эту сторону. Если интерактив не нужен, то от этой части можно отказаться.

Подробнее

Начну разбор с инициализации. Для начала я накидал что-то подобное:

function init() {
  //Canvas init
  var canvas = document.getElementById(‘snow’);

  //Canvas resize
  var W = window.innerWidth;
  var H = window.innerHeight;
  canvas.width = W;
  canvas.height = H;

  //Get context
  var ctx = canvas.getContext(‘2d’);
  ctx.strokeStyle = ‘white’;

  //Prepare array of particles
  var mp = 100;
  var particles = [];
  for (var i = 0; i < mp; i++) {
    particles.push({
      x: Math.random() * W,
      y: Math.random() * H,
      r: Math.random() * 3 + 1
    })
  }
}

Первые три блока можно скопировать из любого «Самоучителя по Canvas для чайников». Получаем конвас, растягиваем на всю страницу и готовимся рисовать белым цветом.

В последнем блоке создаем массив, который будет отвечать за снежинки. Particles — массив снежинок. А каждая снежинка это объект, который «помнит» о своих координатах и диаметре. Для каждой снежинки задаем её начальное состояние: она должна быть где-то на нашем канвасе (случайным образом) и должна получить диаметр от 1 до 4 пикселей.

После этого переходим к отрисовке текущего состояния нашего снега.

function draw(W, H, particles, mp, ctx) {
  //Clearing
  ctx.clearRect(0, 0, W, H);

  //Set white whith opacity 0.8
  ctx.fillStyle = “rgba(255, 255, 255, 0.8)”;

  //Drowing
  ctx.beginPath();
  for (var i = 0; i < mp; i++) {
    var p = particles[i];
    ctx.moveTo(p.x, p.y);
    ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2, true);
  }
  ctx.fill();
}

Тут то же все просто. Чистим поле и заново рисуем каждую снежинку на её координатах и с её диаметром. Для новичков хочу обратить нимание, что мы сначала «накидываем» все в контакст. И только один раз отрисовываем с помощью ctx.fill();

Переходим к анимации. Пишем функцию, которая будет смещать все снежинки вниз. Если их скорость будет одинаковой будет скучно. По этому мы представляем, что большие снежинки ближе, потому и падают быстрее.

function update(W, H, particles, mp, ctx) {
  for (var i = 0; i < mp; i++) {
    var p = particles[i];
    p.y += 1 + p.r/2;

    if (p.y > H) {
      particles[i] = {
        x: Math.random() * W,
        y: -10,
        r: p.r
      };
    }
  }
}

Скорость падения зависит от радиуса. Можно добавить какой-нибудь дополнительный случайный параметр, который вместо радиуса будет влиять на скорость. Но мне показалось, что и так не плохо. Если снежинка вылетела за поле вниз, то сразу перемещаем её в выше верха и смещаем случайно по оси X. Ничего хитрого.

Когда мы все это подготовили нам осталось запустить отрисовку по таймеру. Для этого добавляем в init:

setInterval(function() {
    draw(W, H, particles, mp, ctx);
  }, 50);

И после отрисовки сразу общитывать новое состояние. Для этого вызываем update в конце draw:

update(W, H, particles, mp, ctx);

Интерактивчик

Все. Простой снег готов. Но мне захотелось добавить интерактива. А то никакого профита, что налету снег обсчитываем. Добавляем функцию, которая отслеживает движения мыши и всгда записывает её положение. Для записи я расширил объект body, который точно есть у нашей веб-страницы. Добавляем в init:

document.body.myData = {
    x: 0,
    y: 0
  };
  document.onmousemove = mousemove;

Теперь по умолчанию мышка у нас в верхнем левом углу, но как только пользователь её сдвинет, мы обновим координаты.
Записывать при сдвиге мыши будем с помощью такой функции:

function mousemove(event) {
var mouse_x = mouse_y = 0;
if (document.attachEvent != null) {
mouse_x = window.event.clientX;
mouse_y = window.event.clientY;
} else if (!document.attachEvent && document.addEventListener) {
mouse_x = event.clientX;
mouse_y = event.clientY;
}
document.body.myData.x = mouse_x;
document.body.myData.y = mouse_y;
}

Отследить то мы отследили. Осталось добавить скорость вдоль горизонтальной оси Ox:

var speed_x = (document.body.myData.x – (W / 2)) / W;

Смотрим в какой половине мышь: в правой или левой. И как далеко она от центра. Чем дальше, тем «сильнее ветер».

И осталось добавить сдвиг по Ox:

p.x += speed_x * 10;

Вместо послесловия

Приглашаю уважаемых коллег критиковать мой код и предлагать, как было бы сделать лучше. А новичков задавать вопросы.

Share
Send
3 comments
Михей

привет, я новичок
Разбирал код 2 часа, поскольку до этого не сталкивался с канвасом. Очень интересно! И очень познавательно. Поскольку ты предлагаешь задавать вопросы, то вот он:
почему mousemove такая сложная функция? вот эта конструкция if и if-else что она проверяет? Это какая-то кросс брузерная проверка? первая проверяет интернет эксплорер? и почему через AttachEvent и AddEvent Listener
спасибо

Корейша Виктор

Да, дело в кросс-браузерности.
window.event есть в IE и Хроме, но нет в Фаерфоксе. Зато просто event в ФФ отдаст координаты, а в Хроме нет. attachEvent или addEventListener как раз рассказывают нам о возможностях получить событие напрямую или через window.
Сейчас подумал, что можно было получить событие и попроще. Например так:
var evt = e || window.event

По теме вопрос на Стековерфло. Интересно, что именно поведение Фаерфокса является стандартом

Михей

Вот и второй вопрос:
ты вот тут я так понимаю создаешь объет с 2мя свойствами

document.body.myData = {
x: W/2,
y: 0
};

я наверное не очень разбираюсь в джаваскрипте, но ведь у document.body нет такого свойства-объекта myData? то есть ты его создаешь из воздуха получается?

Корейша Виктор

Да, «из воздуха» очень точное определение. Две важные особенности джаваскрипта по сравнению с другими языками:
1) Все, что не относится к простым типам, является объектами. Массив — объект. Функция — объект.
2) Объекты настолько гибкие, что методы и свойства могут получать прямо в процессе выполнения кода. Тогда, как в других языках программирования нам нужно было быть взять класс, отнаследоваться от него и добавить к нему свойство или метод, в джаваскрипте мы можем брать уже определенный объект засовывать что угодно прямо в него.

Именно за эти два свойства кто-то обожает джаваскрипт, а кто-то ненавидит.

Тут, конечно много еще чего можно про это рассказать. Я постараюсь написать об этом как-нибудь отдельный материал. Хотя, наверняка, пишут и без меня достаточно.

Михей
  1. тогда зачем ты myData приаттачиваешь к document.body? можно получается писать просто myData = {x: 1, x: 1}?
  2. если массив объект? значит что его можно не декларировать? например ты пишешь var particle = [];
    можно ли писать товар particle = [];? без var?
Корейша Виктор

1) Надо было засунуть куда-нибудь поглобальнее. Пространства имен в джаваскрипте тема достаточно сложная для понимания и порождающая кучу ошибок из-за всяких замыканий. Если написать создание объекта внутри функции, то легко нарваться на то, что он будет доступен только внутри функции.
Мне же важно, что бы данные записывались туда откуда их можно брать. Я мог просто глобальный объект завести, но мне хотелось именно такой метод показать. Конечно, не обязательно было именно к body привязываться. Более того, если я захочу когда-нибудь сделать на одной странице несколько снегопадов, то данные о том куда «дуть ветру» буду крепить к каким-нибудь их контейнерам.

2) Джаваскрипт вообще много прощает. И декларирование переменных, в первую очередь. Ключевое слово var отвечает за область видимости переменной, если не включен «строгий режим».
Вот я убрал var у particle и все работает:
http://codepen.io/koreysha/pen/zrKpBy
А еще я там же убрал у переменной mp отвечающей за количество снежинок.
Возможно в старых IE будет ошибка, но в совремнных браузерах все ок.

Popular