Przebyliśmy długą drogę od podstaw przeglądarki. Narzędzia takie jak „Three.js” i „WebGL Studio”, biblioteki animacji, takie jak „Raphael.js” lub „GSAP”, rosnąca popularność „SVG” i coraz bardziej sprzyjające zmiany w „Javascript” w ostatniej dekadzie zmieniły animacje w przeglądarce od sporadycznej nowości do czegoś prawdziwego i przyjemnego do zabawy. Kanwa HTML to świetny sposób na zmoczenie nóg podczas tworzenia bezpośrednio interaktywnych wizualizacji w przeglądarce. Ale animacji jest dużo i najlepiej zacząć od czegoś małego. W tym samouczku nauczymy się, jak stworzyć prostą chmurę cząstek przy użyciu pełnoekranowego płótna, animować ją i sprawić, by reagowała na działania użytkownika.

Pod koniec tego samouczka utworzysz element składający się z
małych kolorowych cząstek, które obracają się wokół pozycji myszy; gdy lewy przycisk myszy jest wciśnięty, efekt cząsteczkowy będzie skalowany tak, aby zakreślił cały ekran. Mamy nadzieję, że ten artykuł da każdemu nowicjuszowi Canvas kilka pomysłów na zabawę.

Wypróbuj demo tutaj: http://particle-vortex.herokuapp.com/

Przeczytaj pełny kod źródłowy tutaj: https://github.com/IsaacBell/Canvas-Particle-Vortex

Zmienne początkowe

Najpierw ustalmy warunki początkowe i zmienne efektu. Dzięki temu w jednym miejscu możemy dokonać globalnych zmian. Tutaj ustawimy niezbędne zmienne dla elementu 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

Ustawiamy zmienną przechowującą rozmiar okna wyświetlania, przekazujemy tę informację do elementu canvas w celu zdefiniowania jego granic. Zmienna r określi promień chmury cząstek. skala zmienia rozmiar chmury, scaleMin pozwala nam ustawić bezwzględnie minimalną skalę chmury, a scaleMax pozwala ustawić maksymalną skalę, jaką osiągnie chmura, gdy użytkownik będzie przytrzymywał mysz. particleCount pozwala zwiększyć lub zmniejszyć liczbę cząstek pojawiających się w elemencie; za dużo i wystąpią problemy z wydajnością. Na potrzeby tego dema 30 FPS będzie wystarczające.

Nasza funkcja inicjująca

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

Oto, co dzieje się w kodzie:

Krok 1

Tworzymy element canvas i ustawiamy odpowiadający mu identyfikator elementu HTML na „myCanvas”. Będziesz potrzebować elementu o tym identyfikatorze w kodzie HTML, aby dopasować.

Krok 2

Dołączamy (dodajemy) element canvas do treści naszego kodu HTML. Jeśli pominiemy ten krok, płótno nigdy się nie pojawi, ponieważ nie jest nigdzie rysowane/renderowane w dokumencie HTML

Krok 3

Jeśli płótno zostało poprawnie ustawione i możemy dokonać konfiguracji za pomocą metody getContext()

-› Ustawiamy kontekst rysowania na „2D”

-› Dodajemy detektory zdarzeń dla zdarzeń myszy i zmiany rozmiaru przeglądarki

-› Wywołujemy funkcję createParticles(), której użyjemy do wygenerowania naszej chmury cząstek

-› Wywołujemy funkcję onResize(), która dopasuje nasz element canvas do potrzebnych proporcji

-› Ustawiamy animLoop() tak, aby powtarzała się z szybkością naszej zmiennej fps

Kolor

Pierwszą funkcją, którą zdefiniujemy, jest colorLuminance(). Potrzebujemy tego, gdy generujemy nasze cząstki; użyjemy tego, gdy rozjaśnimy lub przyciemnimy kolory szesnastkowe, które wygenerujemy podczas tworzenia naszej chmury cząstek. funkcja colorLuminance() przyjmuje dwa argumenty, pierwszy to ciąg szesnastkowy reprezentujący kolor, a drugi to liczba dziesiętna z zakresu od 1 do -1, wskazująca, jak bardzo rozjaśnić lub przyciemnić kolor szesnastkowy.

Więcej szczegółów na temat tego, co się tutaj dzieje, można znaleźć w tym „artykule Craiga Bucklera”.

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

Mając to na uwadze, stwórzmy funkcję, która będzie służyć jako główna część naszego projektu.

Tworzenie naszych cząstek

Użyjemy pętli while, aby utworzyć szereg cząstek o losowych prędkościach ruchu, kolorze wypełnienia i odległości orbity od środka chmury. Wartość fillColor korzysta z funkcji colorLuminance(), którą właśnie dodaliśmy wcześniej; pobaw się tą linią, aby znaleźć zakres kolorów, który Ci się podoba. Prędkość i odległość orbity każdej cząstki są losowane w pewnym stopniu poprzez pomnożenie wartości przez wartość zwróconą przez Math.random(). Jest to bardzo powszechny wzór w przetwarzaniu grafiki/kolorów.

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

Odpowiadanie na słuchaczy zdarzeń

Następnie napiszemy funkcje, które będą uruchamiane przez skonfigurowane wcześniej detektory zdarzeń. Nic zbyt skomplikowanego, po prostu przechowujemy dynamiczną pozycję myszy i status myszy w naszych zmiennych najwyższego poziomu. Na koniec, gdy zmienimy rozmiar okna przeglądarki, zmienimy wymiary elementu canvas.

# 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

Pętla animacji

Teraz o samej pętli renderowania. To tutaj rozgrywa się prawdziwa akcja; nie daj się zastraszyć, patrząc na to.

Oto podsumowanie tego, co robimy:

  • Skalujemy chmurę cząstek do jej maksymalnego lub minimalnego rozmiaru w zależności od tego, czy mysz jest opuszczona
  • Za pomocą kontekstu.fillStyle() ustawiamy kolor rysowania kształtów. Wybieramy czarny z kryciem 55%
  • Za pomocą kontekstu.fillRect() wyznaczamy granice prostokąta płótna, który będziemy wypełniać
  • Używając prostej pętli while, iterujemy po cząstkach oraz obracamy, przesuwamy i delikatnie powiększamy każdą cząstkę, jeśli to konieczne. Im większa jest zmienna fps, tym szybsze będą te zmiany i obroty
  • Po obliczeniu nowego rozmiaru i położenia naszej cząstki w każdej iteracji używamy interfejsu API canvas do narysowania naszego okręgu
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

Na koniec musimy wywołać naszą funkcję inicjującą, aby przeskoczyć pewne rzeczy.

window.onload = init

Wniosek

Mamy nadzieję, że wiesz, jak przenieść w pełni interaktywne wizualizacje do przeglądarki internetowej za pomocą obszaru roboczego. To demo można łatwo rozszerzyć, zoptymalizować lub zmodyfikować. Jest mnóstwo potencjału. Pobaw się zmiennymi, pobierz źródło na Githubie, poeksperymentuj w przeglądarce.

Repozytorium Github: https://github.com/IsaacBell/Canvas-Particle-Vortex