Instantly share code, notes, and snippets.

What would you like to do?
Example canvas icon for canvas-animation-loader
d3 = require 'd3'
module.exports = class SearchIcon
# Construct an instance of a search icon.
# The animation will take {duration} milliseconds, and occupy
# a grid {width}x{height} pixels.
constructor: (duration, width, height) ->
if width != height
throw new ArgumentError("search icon was not rendered in a square")
@_scale = d3.scale.linear().domain([0, width]).range([0, 32])
# the domain of the speed function is number of milliseconds
# after the start of the transition.
# the range is normalized from 0 to 1.
@_speed = d3.scale.linear().domain([0, duration]).range([0, 1])
# Apply sinusoidal easing.
@_ease = d3.ease('sin-in-out')
# each of these scales handles the animation of part of the icon.
# the domain (input range) is over time, from 0 to 1. In other words an
# animation with domain [0, 0.5] would only move for the first half of time.
# the range is the range of output values, usually pixel positions.
# The handleStartPosition is the extension of the magnifying glass handle into the full cross-bar.
_handleStartPosition: d3.scale.linear().domain([0, 0.55]).range([1, 20]).clamp(true)
# The tailDegree is the back of the arc as it unwinds around the magnifying glass head
_tailDegreeScale: d3.scale.linear().domain([0.188, 1]).range([0, Math.PI * 2]).clamp(true)
# the arcPosition is the vertical co-ordinate of the arc as it opens up into an X
_arcPositionScale: d3.scale.linear().domain([0, 0.188]).range([31, 11.5]).clamp(true)
# the headDegree is the front of the arc as it coils from the magnifying glass to straight
_headDegreeScale: d3.scale.linear().domain([0.446, 1.05]).range([0.25, 1]).clamp(true)
# the topBarLength is the extrusion of the top-right bar of the X
_topBarLengthScale: d3.scale.linear().domain([0, 0.446]).range([15, 0]).clamp(true)
# render the icon to the given canvas context at the given time.
# ensure: 0 <= timeInMilliseconds <= duration
render: (ctx, timeInMilliseconds) =>
tDirection = d3.scale.linear().domain([0, 1]).range([0.001, 1]).clamp(true)
t = tDirection(@_ease(@_speed(timeInMilliseconds)))
ctx.clearRect @_scale(0), @_scale(0), @_scale(32), @_scale(32)
ctx.strokeStyle = 'rgb(79, 79, 79)'
ctx.lineWidth = @_scale(2.5)
if t == 0
# draw a cross.
ctx.moveTo @_scale(1), @_scale(1)
ctx.lineTo @_scale(31), @_scale(31)
ctx.moveTo @_scale(31), @_scale(1)
ctx.lineTo @_scale(1), @_scale(31)
return true
else if t == 1
# draw a magnifying glass.
ctx.moveTo @_scale(31), @_scale(31)
ctx.lineTo @_scale(20), @_scale(20)
ctx.arc @_scale(12.25), @_scale(12.25), @_scale(10.75), 0, Math.PI * 2
return true
handleStart = @_handleStartPosition(t)
# move the tail for half the time
tailDegree = @_tailDegreeScale(t)
headDegree = @_headDegreeScale(t)
topBarLength = @_topBarLengthScale(t)
arcPosition = @_arcPositionScale(t)
# we want an arc that is tangent to the cross' top-right bar, and which
# starts at the current arcPosition.
center = (520 - (arcPosition * arcPosition)) / (62 - (2 * arcPosition))
# hack, the line rendering fails for ridiculously tiny numbers
center = Math.max(-10000, center)
radius = Math.sqrt(2 * (16 - center) ** 2)
initialDegree = Math.PI / 2 - Math.atan((1 - center) / (arcPosition - center))
# the magnifying glass handle <-> the top-left, bottom-right line on the cross
ctx.moveTo @_scale(31), @_scale(31)
ctx.lineTo @_scale(handleStart), @_scale(handleStart)
# the magnifying glass head
ctx.arc @_scale(12.25), @_scale(12.25), @_scale(10.75), 1 * Math.PI, tailDegree + 1 * Math.PI
# the arc from the magnifying glass head to the top-right bar
ctx.arc @_scale(center), @_scale(center), @_scale(radius), Math.max(initialDegree, headDegree * Math.PI), headDegree * Math.PI, true
# the top-right bar
ctx.moveTo @_scale(16), @_scale(16)
ctx.lineTo @_scale(16 + topBarLength), @_scale(16 - topBarLength)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment