import Music from '../music'
import {$id, $class, $query, analyzeAudio, angle2rad, drawRoundedPolygon, easeInOutQuad} from '../utils'
class Chino
# The depth of the curves
fluffiness: 20
# Size of the curves
fluffSize: 8
# Initial angle
angle: -17
# Number of flowers
nFlowers: 64
# Particle array
flowers: []
# Animation state
paused: false
# Blinking state
blinking: false
# Fully open
eyesClosed: 135
# Music class for visualization
player: undefined
# Default frequency array
frequencyArray: Array.from Array(64), ->
0
# Center of the canvas
center:
x: undefined
y: undefined
# Main circle size
size: undefined
# Canvas to draw on
canvas: undefined
ctx: undefined
constructor: (@container, sliders, paused = true) ->
@canvas = $query '.circles_chino'
@ctx = @canvas.getContext '2d'
do @resizeCanvas
self = @
do (self) ->
setInterval (->
do self.blink
return
), 5000
# Only do these if on actual page
if sliders
@player = new Music @nFlowers
self = @
do (self) ->
window.addEventListener 'resize', ->
do self.resizeCanvas
# Rotate Tippy vertically
sliders.verticalSlider.on 'slideChange', ->
if @activeIndex is 0
self.rotateTippy -17
else
self.rotateTippy 90
# Rotate Tippy horizontally
sliders.horizontalSlider.on 'slideChange', ->
if @activeIndex is 0
self.rotateTippy 180
else if @activeIndex is 1
self.rotateTippy 90
sliders.verticalSlider.on 'slideChangeTransitionEnd', ->
if @activeIndex is 1
words = $class 'description__word_chino'
for word in words
do (word) ->
setTimeout ->
word.style.transform = 'scaleY(1)'
return
, 500 * do Math.random
do @createFlowers
@createTippy paused
resizeCanvas: =>
@canvas.width = @container.clientWidth * 3
@canvas.height = @container.clientHeight * 2
@center.x = @canvas.width / 3
@center.y = @canvas.height / 2
@size = @container.clientWidth / 3
@fluffiness = @container.clientWidth / 120
# Main circle shouldn't be bigger than 60% of the height of the window
if @size > @container.clientHeight * 0.6
@size = @container.clientHeight * 0.6
do @createFlowers
return
createFlowers: =>
# Reset flowers
@flowers = []
# Create flowers
for i in [0...@nFlowers]
@flowers.push
x: @canvas.width * do Math.random
y: @canvas.height * do Math.random
angle: 180 * do Math.random
scale: -10 * do Math.random # negative value for delay
color: if i % 2 then '255, 244, 219' else '255, 192, 203' # RGB for easy alpha
opacity: 0
index: i
updateFlower: (flower) =>
# Don't do anything until the "delay" has passed
if flower.scale > 0
# Fade out once it reaches half the size
if flower.scale >= 0.5
flower.opacity -= 0.004
# Fade in until it reaches half the size
else
flower.opacity += 0.004
# Reset flower if reached maximum size
if flower.scale > 1
flower.scale = 0
flower.x = @canvas.width * do Math.random
flower.y = @canvas.height * do Math.random
flower.opacity = 0
# Draw flower
do @ctx.beginPath
@ctx.fillStyle = "rgba(#{flower.color}, #{flower.opacity})"
pulse = @frequencyArray[flower.index] / 255
for i in [-1..1] by 2
@ctx.ellipse flower.x, flower.y, (30 * (1 + pulse)) * flower.scale, (50 * (1 + pulse)) * flower.scale, angle2rad(flower.angle + i * 45), 0, 2 * Math.PI
do @ctx.fill
# Increment size and rotation
flower.scale += 0.002
flower.angle += 0.1
rotateTippy: (to) ->
# Current angle
start = @angle
# How much he will rotate
change = to - start
# Half a second duration
duration = 500
currentTime = 0
increment = 5
self = @
do (self) ->
rotate = ->
currentTime += increment
val = easeInOutQuad currentTime, start, change, duration
self.angle = val
if currentTime < duration
setTimeout rotate, increment
return
do rotate
return
drawEar: (angle) =>
# Draw outer part of ear
# Avoid drawing black outline over already drawn areas
@ctx.globalCompositeOperation = 'lighter'
@ctx.fillStyle = '#FFF'
do @ctx.beginPath
drawRoundedPolygon @ctx, [
x: @center.x + (@size - @fluffiness/2) * Math.cos angle2rad @angle + angle - @fluffiness/4
y: @center.y + (@size - @fluffiness/2) * Math.sin angle2rad @angle + angle - @fluffiness/4
,
x: @center.x + (@size + @fluffiness * 3 - @fluffiness/2) * Math.cos angle2rad @angle + angle
y: @center.y + (@size + @fluffiness * 3 - @fluffiness/2) * Math.sin angle2rad @angle + angle
,
x: @center.x + (@size - @fluffiness/2) * Math.cos angle2rad @angle + angle + @fluffiness/4
y: @center.y + (@size - @fluffiness/2) * Math.sin angle2rad @angle + angle + @fluffiness/4
], 10
do @ctx.stroke
do @ctx.fill
# Draw inner part of ear
@ctx.globalCompositeOperation = 'source-over'
@ctx.fillStyle = '#ee7788'
do @ctx.beginPath
drawRoundedPolygon @ctx, [
x: @center.x + (@size + @fluffiness/4) * Math.cos angle2rad @angle + angle - @fluffiness/8
y: @center.y + (@size + @fluffiness/4) * Math.sin angle2rad @angle + angle - @fluffiness/8
,
x: @center.x + (@size + @fluffiness * 1.5 + @fluffiness/4) * Math.cos angle2rad @angle + angle
y: @center.y + (@size + @fluffiness * 1.5 + @fluffiness/4) * Math.sin angle2rad @angle + angle
,
x: @center.x + (@size + @fluffiness/4) * Math.cos angle2rad @angle + angle + @fluffiness/8
y: @center.y + (@size + @fluffiness/4) * Math.sin angle2rad @angle + angle + @fluffiness/8
], 5
do @ctx.fill
drawEye: (angle) =>
@ctx.fillStyle = '#000'
do @ctx.beginPath
distance = @size * 0.7
# If audio is playing
unless @blinking or not @player or @player.audio is undefined or @player.player.paused
start = angle2rad @angle - 90 - @eyesClosed
end = angle2rad @angle + @eyesClosed
# Close eyes
if @eyesClosed isnt 0
@eyesClosed -= 5
else
# Recover form music state
if not @blinking and @eyesClosed is 0
@eyesClosed = 135
# Start opening eyes
if @eyesClosed is 0
@blinking = false
# Close eyes
if @blinking
@eyesClosed -= 5
# Open eyes
else if @eyesClosed isnt 135
@eyesClosed += 5
start = angle2rad @angle + 90 - @eyesClosed
end = angle2rad @angle + 180 + @eyesClosed
@ctx.arc @center.x + ((distance) * Math.cos(angle2rad @angle + angle)), @center.y + ((distance) * Math.sin(angle2rad @angle + angle)), @container.clientWidth / 150, start, end
do @ctx.fill
return
blink: =>
# If audio isnt playing
if not @player or @player.audio is undefined or @player.player.paused
@blinking = true
@eyesClosed = 130
return
# position is either 1 or -1
drawLip: (position) =>
@ctx.quadraticCurveTo(
@center.x + ((@size * 0.42) * Math.cos(angle2rad @angle + (-45 + position * 5))),
@center.y + ((@size * 0.42) * Math.sin(angle2rad @angle + (-45 + position * 5))),
@center.x + ((@size * 0.47) * Math.cos(angle2rad @angle + (-45 + position * 10))),
@center.y + ((@size * 0.47) * Math.sin(angle2rad @angle + (-45 + position * 10)))
)
createTippy: (paused) =>
if window.innerWidth >= 768 and not @paused
@ctx.lineWidth = @container.clientWidth / 300
# Clear canvas
@ctx.clearRect 0, 0, @canvas.width, @canvas.height
# Analyze audio
if @player and @player.audio isnt undefined
analyzeAudio @player, @fft
@frequencyArray = @player.dataArray
# Draw flowers
for flower in @flowers
@updateFlower flower
@ctx.fillStyle = '#FFF'
do @ctx.beginPath
# Move to right side of the body
@ctx.moveTo @center.x + @size, @center.y
# Draw Tippy's body (fluffy)
for i in [0..360] by @fluffSize
@ctx.quadraticCurveTo(
@center.x + ((@size + @fluffiness) * Math.cos(angle2rad @angle + i + (@fluffSize) / 2)),
@center.y + ((@size + @fluffiness) * Math.sin(angle2rad @angle + i + (@fluffSize) / 2)),
@center.x + (@size * Math.cos(angle2rad @angle + i + (@fluffSize))),
@center.y + (@size * Math.sin(angle2rad @angle + i + (@fluffSize)))
)
do @ctx.stroke
do @ctx.fill
# Draw ears
@drawEar -25
@drawEar -65
# Draw eyes
@drawEye -25
@drawEye -65
# Nose position
nose =
x: @center.x + ((@size * 0.5) * Math.cos(angle2rad @angle - 45))
y: @center.y + ((@size * 0.5) * Math.sin(angle2rad @angle - 45))
radius: @container.clientWidth / 480
# Draw nose
do @ctx.beginPath
@ctx.arc nose.x, nose.y, nose.radius, 0, 2 * Math.PI
do @ctx.fill
# Mouth vertical line position
line =
x: @center.x + ((@size * 0.47) * Math.cos(angle2rad @angle - 45))
y: @center.y + ((@size * 0.47) * Math.sin(angle2rad @angle - 45))
@ctx.lineWidth = @container.clientWidth / 600
# Draw mouth vertical line
do @ctx.beginPath
@ctx.moveTo nose.x, nose.y
@ctx.lineTo line.x, line.y
# Draw lips
@drawLip -1
@ctx.moveTo line.x, line.y
@drawLip 1
do @ctx.stroke
# @angle += 0.1
if paused is true
@paused = true
requestAnimationFrame @createTippy
return
export default Chino