Мы прошли долгий путь от основ браузера. Такие инструменты, как Three.js и WebGL Studio, библиотеки анимации, такие как Raphael.js или GSAP, растущая популярность SVG и все более благоприятные изменения Javascript за последнее десятилетие изменили анимацию. в браузере из случайной новинки во что-то реальное и интересное для игры. HTML-холст — это отличный способ начать создавать интерактивные визуальные эффекты прямо в браузере. Но анимации много, и лучше всего начать с малого. В этом уроке мы узнаем, как создать простое облако частиц, используя полноэкранный холст, анимировать его и заставить реагировать на действия пользователя.

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

Попробуйте демо здесь: http://particle-vortex.herokuapp.com/

Полный исходный код читайте здесь: https://github.com/IsaacBell/Canvas-Particle-Vortex

Начальные переменные

Давайте сначала настроим начальные условия и переменные для эффекта. Таким образом, мы можем вносить глобальные изменения в одном месте. Здесь мы установим необходимые переменные для элемента canvas.

# Initial Setup
fps = 30 
width = window.innerWidth
height = window.innerHeight
r = 70
scale = 1
scaleMin = 12.5
scaleMax = 100 
particleCount = 250
canvas = undefined    # You can of course omit this line
context = undefined   # Ditto
particles = undefined # Ditto
mouseX = width * 0.5
mouseY = height * 0.5
isMouseDown = false

Мы устанавливаем переменную для хранения размера окна дисплея, мы передадим эту информацию элементу холста, чтобы определить его границы. Переменная r будет определять радиус облака частиц. scale изменяет размер облака, scaleMin позволяет нам установить абсолютный минимальный масштаб облака, а scaleMax позволяет установить максимальный масштаб, которого достигнет облако, когда пользователь удерживает мышь. particleCount позволяет увеличить или уменьшить количество частиц, в которых появляется элемент; слишком много, и мы столкнемся с проблемами производительности. Для этой демонстрации вполне подойдет 30 кадров в секунду.

Наша функция инициализации

init = ->
  # Create canvas element
  canvas = document.createElement('canvas')
  canvas.id  = 'myCanvas'
  document.body.appendChild(canvas)
  if canvas and canvas.getContext
    context = canvas.getContext('2d')
    # Event handlers
    window.addEventListener 'mousemove', onMouseMove, false
    window.addEventListener 'mousedown', onMouseDown, false
    window.addEventListener 'mouseup', onMouseUp, false
    window.addEventListener 'onResize', onResize, false
    createParticles()
    onResize()
    setInterval animLoop, 1000 / fps
  return

Вот что здесь происходит в коде:

Шаг 1

Мы создаем элемент холста и устанавливаем для него идентификатор соответствующего HTML-элемента «myCanvas». Вам понадобится элемент с этим идентификатором в вашем HTML для сопоставления.

Шаг 2

Мы присоединяем (добавляем) элемент холста к телу нашего HTML. Если мы пропустим этот шаг, холст никогда не появится, так как он нигде не рисуется/рендерится в HTML-документе.

Шаг 3

Если холст настроен правильно и мы можем выполнить настройку с помощью getContext()

-> Мы устанавливаем наш контекст рисования на «2D»

-› Мы добавляем прослушиватели событий для событий мыши и изменения размера браузера

-> Мы вызываем createParticles(), который мы будем использовать для создания нашего облака частиц

-> Мы вызываем onResize(), который подгоняет наш элемент холста под нужные пропорции.

-> Мы устанавливаем animLoop() для повторения со скоростью нашей переменной fps

Цвет

Первая функция, которую мы определим, это colorLuminance(). Нам это нужно, когда мы генерируем наши частицы; мы будем использовать это, когда будем осветлять или затемнять шестнадцатеричные цвета, которые мы будем генерировать при создании нашего облака частиц. colorLuminance() принимает два аргумента, первый из которых представляет собой шестнадцатеричную строку, представляющую цвет, а второй представляет собой десятичное число от 1 до -1, указывающее, насколько осветлить или затемнить шестнадцатеричный цвет.

Для получения более подробной информации о том, что здесь происходит, ознакомьтесь с этой статьей Крейга Баклера.

colorLuminance = (hex, lum) ->
  # validate hex string
  hex = String(hex).replace(/[^0-9a-f]/gi, '') 
  if hex.length < 6
    hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]
  lum = lum or 0
  # convert to decimal and change luminosity
  rgb = '#'
  c = undefined
  i = undefined
  i = 0
  while i < 3
    c = parseInt(hex.substr(i * 2, 2), 16)
    c = Math.round(Math.min(Math.max(0, c + c * lum), 255)).toString(16)
    rgb += ('00' + c).substr(c.length)
    i++
  return rgb

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

Создание наших частиц

Мы будем использовать цикл while для создания массива частиц со случайными скоростями движения, цветом заливки и расстоянием по орбите от центра облака. Значение fillColor использует функцию colorLuminance(), которую мы только что добавили ранее; поиграйте с этой линией, чтобы найти цветовую гамму, которая вам нравится. Скорость и орбитальное расстояние каждой частицы в некоторой степени рандомизированы путем умножения значений, умноженных на значение, возвращаемое Math.random(). Это очень распространенный шаблон в обработке графики/цвета.

createParticles = ->
  particles = []
  i = 0
  while i < particleCount
    particle = 
      size: 5
      position:
        x: mouseX
        y: mouseY
      offset:
        x: 0
        y: 0
      shift:
        x: mouseX
        y: mouseY
      speed: 0.02 + Math.random() * 0.02
      targetSize: 1
      fillColor: colorLuminance('#' + Math.random().toString(16), -0.23)
      orbit: r / 3 * Math.random()
    particles.push particle
    i++
  return

Ответ прослушивателям событий

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

# Simple event Listener functions
onMouseMove = (e) ->
  mouseX = e.clientX - ((window.innerWidth - width) * .5)
  mouseY = e.clientY - ((window.innerHeight - height) * .5)
  return
onMouseDown = ->
  isMouseDown = true
onMouseUp = ->
  isMouseDown = false
onResize = ->
  width = window.innerWidth
  height = window.innerHeight
  canvas.width = width
  canvas.height = height
  return

Цикл анимации

Теперь о самом цикле рендеринга. Вот где настоящее действие; не пугайтесь, глядя на это.

Вот разбивка того, что мы делаем:

  • Масштабируем облако частиц до максимального или минимального размера в зависимости от того, нажата ли мышь.
  • С помощью context.fillStyle() мы устанавливаем цвет для рисования фигур. Мы выбираем черный цвет с непрозрачностью 55%.
  • С помощью context.fillRect() мы рисуем границы прямоугольника холста, который мы будем заполнять.
  • Используя простой цикл while, мы перебираем частицы и поворачиваем, сдвигаем и плавно увеличиваем каждую по мере необходимости. Чем быстрее переменная fps, тем быстрее эти сдвиги и повороты будут казаться глазу.
  • После того, как мы рассчитали новый размер и положение нашей частицы на каждой итерации, мы используем API холста для рисования нашего круга.
animLoop = ->
  if isMouseDown
    # Expand the cloud when mouse is clicked down
    scale += (scaleMax - scale) * 0.2
  else
    # Or else shrink the cloud down
    scale -= (scale - scaleMin) * 0.2
  # Apply whichever change we set above
  scale = Math.min(scale, scaleMax)
  # Set our line opacity and limit our drawing board 
  # size to the size of the screen
  context.fillStyle = 'rgba(0,0,0,0.55)'
  context.fillRect 0, 0, context.canvas.width, context.canvas.height
  
  i = 0
  len = particles.length
  
  while i < len
    particle = particles[i]
    
    # Rotation
    particle.offset.x += particle.speed * 0.7
    particle.offset.y += particle.speed * 0.7
    
    # Follow the mouse, with a bit of blur/lag effect
    particle.shift.x += (mouseX - (particle.shift.x)) * particle.speed * 0.6
    particle.shift.y += (mouseY - (particle.shift.y)) * particle.speed * 0.6
    
    # Shift the particles accordingly, using a cosine function
    particle.position.x = particle.shift.x + Math.cos(i + particle.offset.x) * particle.orbit * scale
    particle.position.y = particle.shift.y + Math.sin(i + particle.offset.y) * particle.orbit * scale
    
    # Limit our animation to the screen bounds
    particle.position.x = Math.max(Math.min(particle.position.x, width), 0)
    particle.position.y = Math.max(Math.min(particle.position.y, height), 0)
    particle.size += (particle.targetSize - (particle.size)) * 0.05
    
    if Math.round(particle.size) == Math.round(particle.targetSize)
      particle.targetSize = 1 + Math.random() * 10
      
    # Finally, let's do some drawing!
      
    context.beginPath()
      
    # Select line color at random
    context.fillStyle = particle.fillColor
    context.strokeStyle = particle.fillColor
    context.lineWidth = particle.size
    context.moveTo particle.position.x, particle.position.y
    context.lineTo particle.position.x, particle.position.y
    context.stroke()
    context.arc particle.position.x, particle.position.y, particle.size / 2, 0, Math.PI * 2, true
    context.fill()
    i++
  
  return

Наконец, нам нужно вызвать нашу функцию инициализации, чтобы сбросить все.

window.onload = init

Вывод

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

Репозиторий Github: https://github.com/IsaacBell/Canvas-Particle-Vortex