Skip to content

Instantly share code, notes, and snippets.

@rodaine
Last active December 23, 2015 03:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rodaine/6575633 to your computer and use it in GitHub Desktop.
Save rodaine/6575633 to your computer and use it in GitHub Desktop.
A Pen by Chris Roche.
#-------------------------------------------------------
# THE MAGIC
#-------------------------------------------------------
# grab the canvas element
el = document.getElementById 'c'
$el = $(el)
# gimme that full screen
el.width = $el.innerWidth()
el.height = $el.innerHeight()
# get the vectors to define the bounds
# giving a bit of wiggle room for overflow (makes for a prettier pattern)
topLeft = new Vector()
topLeft.x -= el.width * .25
topLeft.y -= el.height * .25
bottomRight = new Vector el.width, el.height
bottomRight.x += el.width * .25
bottomRight.y += el.height * .25
# make the bounds for rendering the triangles
bounds = new Bounds topLeft, bottomRight
# create the canvas object with the canvas element
canvas = new Canvas el
# add in the triangles
canvas.addTriangle new Triangle bounds for [0...config.triangles]
# profit!
canvas.start()
#-------------------------------------------------------
# BOUND - A rectangular boundary for drawing
#-------------------------------------------------------
class Bounds
topLeft: undefined
bottomRight: undefined
constructor: (@topLeft = new Vector, @bottomRight = new Vector) ->
minX: -> @topLeft.x
maxX: -> @bottomRight.x
minY: -> @topLeft.y
maxY: -> @bottomRight.y
#-------------------------------------------------------
# CANVAS - The animation logic and wrapper for the canvas
#-------------------------------------------------------
class Canvas
updateStep: 1000/60
el: undefined
context: undefined
triangles: undefined
previous: undefined
lag: undefined
constructor: (@el) ->
@context = @el.getContext('2d')
@triangles = []
@previous = new Date().getTime()
@lag = 0.0
# add a Triangle to the stack to be animated
addTriangle: (triangle) -> @triangles.push triangle
# updates all the triangles on the stack
update: ->
triangle.update() for triangle in @triangles
return
# renders all the triangles on the stack
render: (partial) ->
@context.clearRect 0, 0, @el.width, @el.height
triangle.render(@context, partial) for triangle in @triangles
return
# run at each animation frame
# inspired by Bob Nystrom: http://gameprogrammingpatterns.com/
loop: ->
current = new Date().getTime()
@lag += current - @previous
@previous = current
while @lag >= @updateStep
@update()
@lag -= @updateStep
@render @lag / @updateStep
# start the animation loop using requestAnimationFrame wrapper
start: ->
window.requestAnimFrame => @start()
@loop()
#-------------------------------------------------------
# CONFIGURATION - Feel free to mess with these values
#-------------------------------------------------------
config =
#RENDERING SETTINGS
triangles: 30 # number of triangles to render
speed: 2 # speed of animation ... approximately pixels per update
#TRIANGLE COLORS
redMin: 0 # minimum red value (0 - 255)
redMax: 255 # maximum red value
greenMin: 0 # minimum green value
greenMax: 32 # maximum green value
blueMin: 0 # minimum blue value
blueMax: 32 # maximum blue value
opacityMin: .05 # minimum opacity (0 - 1)
opacityMax: .2 # maximum opacity
# Clamps a numeric value between a minimum and maximum (inclusively)
clamp = (value, min, max) -> Math.min(Math.max(value, min), max)
# Gets a random number between min (inclusive) and max (exclusive)
rand = (min = 0, max = 1) -> Math.random() * (max - min) + min
# requestAnimationFrame compatibility/fallback wrapper
# a la Paul Irish: http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
(callback, element) -> window.setTimeout callback, 1000 / 60
<canvas id="c"></canvas>

Prism

HTML5 Canvas based animations reminiscent of the Bezier screensaver from Windows. Essentially, it is a bunch of triangles of random color/opacity whose vertices translate independently. Tweak the values at in the config object at the top of the JS editor to affect the number of triangles, the speed of the animation, and the color pallette used.

Thanks to Paul Irish for his article on requestAnimationFrame (http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/), and Bob Nystrom for his book on Game Programming Patterns (http://gameprogrammingpatterns.com/) which inspired the loop logic for this pen.

A Pen by Chris Roche on CodePen.

License.

#-------------------------------------------------------
# CONFIGURATION - Feel free to mess with these values
#-------------------------------------------------------
config =
#RENDERING SETTINGS
triangles: 30 # number of triangles to render
speed: 2 # speed of animation ... approximately pixels per update
#TRIANGLE COLORS
redMin: 0 # minimum red value (0 - 255)
redMax: 255 # maximum red value
greenMin: 0 # minimum green value
greenMax: 32 # maximum green value
blueMin: 0 # minimum blue value
blueMax: 32 # maximum blue value
opacityMin: .05 # minimum opacity (0 - 1)
opacityMax: .2 # maximum opacity
# Clamps a numeric value between a minimum and maximum (inclusively)
clamp = (value, min, max) -> Math.min(Math.max(value, min), max)
# Gets a random number between min (inclusive) and max (exclusive)
rand = (min = 0, max = 1) -> Math.random() * (max - min) + min
# requestAnimationFrame compatibility/fallback wrapper
# a la Paul Irish: http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
(callback, element) -> window.setTimeout callback, 1000 / 60
#-------------------------------------------------------
# VECTOR - A point on the canvas or a bearing
#-------------------------------------------------------
class Vector
x: 0
y: 0
constructor: (@x = 0, @y = 0) ->
# Generates a random vector within the bounds
@random: (bounds) =>
x = rand bounds.minX(), bounds.maxX()
y = rand bounds.minY(), bounds.maxY()
new @ x, y
# Detects whether or not this vertex is out of the bounds
outOfBounds: (bounds) ->
bounds.minX() >= @x ||
bounds.maxX() <= @x ||
bounds.minY() >= @y ||
bounds.maxY() <= @y
# Clamps the vector to a bounds
clamp: (bounds) ->
@x = clamp @x, bounds.minX(), bounds.maxX()
@y = clamp @y, bounds.minY(), bounds.maxY()
@
# Adds another vector to this one
addVector: (vector) ->
@x += vector.x
@y += vector.y
@
# performs a scalar product against the vector
scalarProduct: (scalar) ->
@x *= scalar
@y *= scalar
@
# clamps and reflects a vector if it's out of bounds
reflectAndClamp: (bounds, bearing) ->
if not @outOfBounds bounds then return @
if @x <= bounds.minX() or @x >= bounds.maxX() then bearing.x *= -1
if @y <= bounds.minY() or @y >= bounds.maxY() then bearing.y *= -1
@clamp(bounds)
@
#-------------------------------------------------------
# BOUND - A rectangular boundary for drawing
#-------------------------------------------------------
class Bounds
topLeft: undefined
bottomRight: undefined
constructor: (@topLeft = new Vector, @bottomRight = new Vector) ->
window.bounds = @
minX: -> @topLeft.x
maxX: -> @bottomRight.x
minY: -> @topLeft.y
maxY: -> @bottomRight.y
#-------------------------------------------------------
# TRIANGLE - Polygon to be drawn onto the canvas
#-------------------------------------------------------
class Triangle
red: undefined
green: undefined
blue: undefined
opacity: undefined
bounds: undefined
vertices: undefined
bearings: undefined
bearingBounds: new Bounds(new Vector(-1, -1), new Vector(1, 1))
# creates a random triangle within the bounds
constructor: (@bounds = new Bounds) ->
@red = Math.floor rand config.redMin, config.redMax
@green = Math.floor rand config.greenMin, config.greenMax
@blue = Math.floor rand config.blueMin, config.blueMax
@opacity = rand config.opacityMin, config.opacityMax
@vertices = []
@bearings = []
for [0...3]
@vertices.push Vector.random(@bounds)
@bearings.push Vector.random(@bearingBounds).scalarProduct config.speed
# updates the position of each of the triangle vertices by its bearing
update: ->
for i in [0...3]
vertex = @vertices[i]
bearing = @bearings[i]
vertex.addVector bearing
vertex.reflectAndClamp @bounds, bearing
# renders the triangle in the canvas context
# includes an optional partialStep for extrapolating lag position
render: (context, partialStep = 0) ->
points = []
for i in [0...3]
bearing = @bearings[i]
vertex = @vertices[i]
points.push
x: parseInt(vertex.x + partialStep * bearing.x)
y: parseInt(vertex.y + partialStep * bearing.y)
context.fillStyle = "rgba(#{@red},#{@green},#{@blue},#{@opacity})"
context.beginPath()
context.moveTo points[0].x, points[0].y
context.lineTo(points[i].x, points[i].y) for i in [2..0]
context.closePath()
context.fill()
@
#-------------------------------------------------------
# CANVAS - The animation logic and wrapper for the canvas
#-------------------------------------------------------
class Canvas
updateStep: 1000/60
el: undefined
context: undefined
triangles: undefined
previous: undefined
lag: undefined
constructor: (@el) ->
@context = @el.getContext('2d')
@triangles = []
@previous = new Date().getTime()
@lag = 0.0
# add a Triangle to the stack to be animated
addTriangle: (triangle) -> @triangles.push triangle
# updates all the triangles on the stack
update: ->
triangle.update() for triangle in @triangles
return
# renders all the triangles on the stack
render: (partial) ->
@context.clearRect 0, 0, @el.width, @el.height
triangle.render(@context, partial) for triangle in @triangles
return
# run at each animation frame
# inspired by Bob Nystrom: http://gameprogrammingpatterns.com/
loop: ->
current = new Date().getTime()
@lag += current - @previous
@previous = current
while @lag >= @updateStep
@update()
@lag -= @updateStep
@render @lag / @updateStep
# start the animation loop using requestAnimationFrame wrapper
start: ->
window.requestAnimFrame => @start()
@loop()
#-------------------------------------------------------
# THE MAGIC
#-------------------------------------------------------
# grab the canvas element
el = document.getElementById 'c'
$el = $(el)
# gimme that full screen
el.width = $el.innerWidth()
el.height = $el.innerHeight()
console.log el.width
console.log el.height
# get the vectors to define the bounds
# giving a bit of wiggle room for overflow (makes for a prettier pattern)
topLeft = new Vector()
topLeft.x -= el.width * .25
topLeft.y -= el.height * .25
bottomRight = new Vector el.width, el.height
bottomRight.x += el.width * .25
bottomRight.y += el.height * .25
# make the bounds for rendering the triangles
bounds = new Bounds topLeft, bottomRight
# create the canvas object with the canvas element
canvas = new Canvas el
# add in the triangles
canvas.addTriangle new Triangle bounds for [0...config.triangles]
# profit!
canvas.start()
@import "compass";
body {
background: #efefef;
overflow: hidden;
}
#c {
width: 100%;
height: 1080px;
}
#-------------------------------------------------------
# TRIANGLE - Polygon to be drawn onto the canvas
#-------------------------------------------------------
class Triangle
red: undefined
green: undefined
blue: undefined
opacity: undefined
bounds: undefined
vertices: undefined
bearings: undefined
bearingBounds: new Bounds(new Vector(-1, -1), new Vector(1, 1))
# creates a random triangle within the bounds
constructor: (@bounds = new Bounds) ->
@red = Math.floor rand config.redMin, config.redMax
@green = Math.floor rand config.greenMin, config.greenMax
@blue = Math.floor rand config.blueMin, config.blueMax
@opacity = rand config.opacityMin, config.opacityMax
@vertices = []
@bearings = []
for [0...3]
@vertices.push Vector.random(@bounds)
@bearings.push Vector.random(@bearingBounds).scalarProduct config.speed
# updates the position of each of the triangle vertices by its bearing
update: ->
for i in [0...3]
vertex = @vertices[i]
bearing = @bearings[i]
vertex.addVector bearing
vertex.reflectAndClamp @bounds, bearing
# renders the triangle in the canvas context
# includes an optional partialStep for extrapolating lag position
render: (context, partialStep = 0) ->
points = []
for i in [0...3]
bearing = @bearings[i]
vertex = @vertices[i]
points.push
x: parseInt(vertex.x + partialStep * bearing.x)
y: parseInt(vertex.y + partialStep * bearing.y)
context.fillStyle = "rgba(#{@red},#{@green},#{@blue},#{@opacity})"
context.beginPath()
context.moveTo points[0].x, points[0].y
context.lineTo(points[i].x, points[i].y) for i in [2..0]
context.closePath()
context.fill()
@
#-------------------------------------------------------
# VECTOR - A point on the canvas or a bearing
#-------------------------------------------------------
class Vector
x: 0
y: 0
constructor: (@x = 0, @y = 0) ->
# Generates a random vector within the bounds
@random: (bounds) =>
x = rand bounds.minX(), bounds.maxX()
y = rand bounds.minY(), bounds.maxY()
new @ x, y
# Detects whether or not this vertex is out of the bounds
outOfBounds: (bounds) ->
bounds.minX() >= @x ||
bounds.maxX() <= @x ||
bounds.minY() >= @y ||
bounds.maxY() <= @y
# Clamps the vector to a bounds
clamp: (bounds) ->
@x = clamp @x, bounds.minX(), bounds.maxX()
@y = clamp @y, bounds.minY(), bounds.maxY()
@
# Adds another vector to this one
addVector: (vector) ->
@x += vector.x
@y += vector.y
@
# performs a scalar product against the vector
scalarProduct: (scalar) ->
@x *= scalar
@y *= scalar
@
# clamps and reflects a vector if it's out of bounds
reflectAndClamp: (bounds, bearing) ->
if not @outOfBounds bounds then return @
if @x <= bounds.minX() or @x >= bounds.maxX() then bearing.x *= -1
if @y <= bounds.minY() or @y >= bounds.maxY() then bearing.y *= -1
@clamp(bounds)
@
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment