├── .babelrc ├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── app └── index.html ├── lib ├── createLines.js ├── createScene.js ├── drawArc.js ├── index.js ├── solutions │ ├── createScene1.js │ ├── createScene2.js │ ├── drawArc1.js │ ├── drawArc2.js │ ├── index1.js │ └── index2.js └── utils.js ├── package.json └── screenshots ├── 1.png ├── 2.png ├── 3.png ├── 4.png └── images.png /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: [ "es2015" ] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | test 7 | test.js 8 | demo/ 9 | .npmignore 10 | LICENSE.md -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Jam3 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jam3-lesson-canvas2d 2 | 3 | Some code for a workshop on Canvas2D at Jam3. Some of the outputs of the workshop: 4 | 5 | 6 | 7 | Some of the topics include: 8 | 9 | - Setting up a retina-scaled Canvas 2D 10 | - Canvas2D API 11 | - transforms (scale, rotate) 12 | - rectangle fills 13 | - paths and line strokes 14 | - circle rendering 15 | - equation of a circle 16 | - simplex noise 17 | 18 | ## Usage 19 | 20 | ```sh 21 | git clone https://github.com/Jam3/jam3-lesson-canvas2d.git 22 | cd jam3-lesson-canvas2d 23 | npm install 24 | npm start 25 | ``` 26 | 27 | Now open `localhost:9966` in your browser. 28 | 29 | ## Code 30 | 31 | The code is broken up into 3 parts: 32 | 33 | - Boilerplate and rectangles with `context.fillRect()` 34 | - Arcs with `context.arc()` 35 | - Drawing arcs with noise and `context.lineTo()` 36 | 37 | You can see the `lib/solutions` folder for the different steps, or checkout the `final` branch for the final output. 38 | 39 | ## Further Work 40 | 41 | The code here lays the groundwork for slightly more complex generative artwork. By removing the `clearRect` and `fillRect` calls each frame and selecting a different colour palette, you can end up with images like this: 42 | 43 | 44 | 45 | 46 | 47 | 48 | See the `paint` branch for an example. 49 | 50 | ## License 51 | 52 | MIT, see [LICENSE.md](http://github.com/Jam3/jam3-lesson-canvas2d/blob/master/LICENSE.md) for details. 53 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | jam3-lesson-canvas2d 7 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /lib/createLines.js: -------------------------------------------------------------------------------- 1 | const randomFloat = require('random-float'); 2 | 3 | module.exports = function () { 4 | // Create N "line" objects which will rotate 5 | // around the center of the screen. 6 | const lineCount = 100; 7 | let lines = []; 8 | for (let i = 0; i < lineCount; i++) { 9 | lines[i] = createLine(); 10 | } 11 | 12 | return lines; 13 | 14 | // creates a "line" object with some random properties 15 | function createLine () { 16 | return { 17 | angleStart: randomFloat(-Math.PI * 2, Math.PI * 2), 18 | angleLength: randomFloat(Math.PI * 0.01, Math.PI * 0.25), 19 | thickness: randomFloat(0.5, 2), 20 | distanceFromCenter: randomFloat(0.65, 1), 21 | rotation: randomFloat(-Math.PI * 2, Math.PI * 2), 22 | rotationDirection: randomFloat(1) > 0.5 ? 1 : -1, 23 | rotationSpeed: randomFloat(0.05, 0.25) 24 | }; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /lib/createScene.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: "off" */ 2 | const createLines = require('./createLines'); 3 | const drawArc = require('./drawArc'); 4 | 5 | module.exports = function (app) { 6 | // global time counter in seconds 7 | let time = 0; 8 | 9 | return { 10 | draw 11 | }; 12 | 13 | function draw (dt) { 14 | time += dt / 1000; 15 | 16 | // get app properties 17 | const { width, height, context } = app; 18 | 19 | // this is how much we will scale our circle by 20 | const radialScale = (1 / 3) * Math.min(width, height); 21 | 22 | // draw a circle 23 | context.beginPath(); 24 | context.arc(width / 2, height / 2, radialScale, 0, Math.PI * 2); 25 | context.lineWidth = 4; 26 | context.strokeStyle = 'black'; 27 | context.stroke(); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /lib/drawArc.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: "off" */ 2 | const simplex = new (require('simplex-noise'))(); 3 | 4 | const { 5 | computeStepsForArc, 6 | distanceBetweenAngles 7 | } = require('./utils'); 8 | 9 | module.exports = function (context, line, radius, angleStart, angleEnd, time = 0) { 10 | context.rotate(line.rotation); 11 | context.beginPath(); 12 | context.arc(0, 0, radius, angleStart, angleEnd); 13 | context.stroke(); 14 | context.rotate(-line.rotation); 15 | }; 16 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: "off" */ 2 | const createLoop = require('raf-loop'); 3 | const createScene = require('./createScene'); 4 | 5 | // get Canvas2D 6 | const canvas = document.querySelector('#canvas'); 7 | const context = canvas.getContext('2d'); 8 | 9 | // pixel density, no higher than 2x for performance 10 | const pixelRatio = Math.min(2, window.devicePixelRatio); 11 | 12 | // an object holding our application state 13 | const app = { 14 | canvas, 15 | context, 16 | pixelRatio, 17 | width: 0, // will be set on resize() 18 | height: 0 19 | }; 20 | 21 | // listen for window resizing 22 | window.addEventListener('resize', resize); 23 | 24 | // set initial sizes before setting up scene & loop 25 | resize(); 26 | 27 | // start a draw loop 28 | createLoop(draw).start(); 29 | 30 | function resize () { 31 | // set up a retina-scaled canvas 32 | app.width = window.innerWidth; 33 | app.height = window.innerHeight; 34 | canvas.width = Math.floor(app.width * pixelRatio); 35 | canvas.height = Math.floor(app.height * pixelRatio); 36 | canvas.style.width = `${app.width}px`; 37 | canvas.style.height = `${app.height}px`; 38 | } 39 | 40 | function draw (dt) { 41 | const { width, height } = app; 42 | 43 | // push context transform state 44 | context.save(); 45 | 46 | // scale everything so it works with pixel ratio 47 | context.scale(pixelRatio, pixelRatio); 48 | 49 | // clear canvas every frame 50 | context.clearRect(0, 0, width, height); 51 | 52 | // draw some shapes! 53 | context.fillRect(20, 30, 25, 40); 54 | 55 | // pop context transform state 56 | context.restore(); 57 | } 58 | -------------------------------------------------------------------------------- /lib/solutions/createScene1.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: "off" */ 2 | const createLines = require('./createLines'); 3 | const drawArc = require('./drawArc'); 4 | 5 | module.exports = function (app) { 6 | // global time counter in seconds 7 | let time = 0; 8 | 9 | return { 10 | draw 11 | }; 12 | 13 | function draw (dt) { 14 | time += dt / 1000; 15 | 16 | // get app properties 17 | const { width, height, context } = app; 18 | 19 | // this is how much we will scale our circle by 20 | const radialScale = (1 / 3) * Math.min(width, height); 21 | 22 | // draw a circle 23 | context.beginPath(); 24 | context.arc(width / 2, height / 2, radialScale, 0, Math.PI * 2); 25 | context.lineWidth = 4; 26 | context.strokeStyle = 'black'; 27 | context.stroke(); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /lib/solutions/createScene2.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: "off" */ 2 | const createLines = require('./createLines'); 3 | const drawArc = require('./drawArc'); 4 | 5 | module.exports = function (app) { 6 | const lines = createLines(); 7 | 8 | // global time counter in seconds 9 | let time = 0; 10 | 11 | return { 12 | draw 13 | }; 14 | 15 | function draw (dt) { 16 | time += dt / 1000; 17 | 18 | // get app properties 19 | const { width, height, context } = app; 20 | 21 | // push the canvas transform state 22 | context.save(); 23 | 24 | // make (0, 0) the center of the screen 25 | const cx = width / 2; 26 | const cy = height / 2; 27 | context.translate(cx, cy); 28 | 29 | // this is how much we will scale our circle by 30 | const radialScale = (1 / 3) * Math.min(width, height); 31 | 32 | // now draw each line 33 | lines.forEach((line, i) => { 34 | // set up line properties before we draw it 35 | context.strokeStyle = '#000'; 36 | context.lineWidth = line.thickness; 37 | context.lineJoin = 'round'; 38 | context.lineCap = 'square'; 39 | 40 | // where to draw the line 41 | const radius = line.distanceFromCenter * radialScale; 42 | const angleStart = line.angleStart; 43 | const angleEnd = angleStart + line.angleLength; 44 | 45 | // draw the line with a stroke 46 | drawArc(context, line, radius, angleStart, angleEnd, time); 47 | 48 | // rotate in one direction or another 49 | var deltaRotation = line.rotationSpeed * dt / 1000; 50 | line.rotation += deltaRotation * line.rotationDirection; 51 | }); 52 | 53 | // pop the context transform state 54 | context.restore(); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /lib/solutions/drawArc1.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: "off" */ 2 | const simplex = new (require('simplex-noise'))(); 3 | 4 | const { 5 | computeStepsForArc, 6 | distanceBetweenAngles 7 | } = require('./utils'); 8 | 9 | module.exports = function (context, line, radius, angleStart, angleEnd, time = 0) { 10 | context.rotate(line.rotation); 11 | context.beginPath(); 12 | context.arc(0, 0, radius, angleStart, angleEnd); 13 | context.stroke(); 14 | context.rotate(-line.rotation); 15 | }; 16 | -------------------------------------------------------------------------------- /lib/solutions/drawArc2.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: "off" */ 2 | const simplex = new (require('simplex-noise'))(); 3 | 4 | const { 5 | computeStepsForArc, 6 | distanceBetweenAngles 7 | } = require('./utils'); 8 | 9 | module.exports = function (context, line, radius, angleStart, angleEnd, time = 0) { 10 | context.beginPath(); 11 | 12 | // figure out how many segments in this arc 13 | const steps = computeStepsForArc(radius, angleStart, angleEnd); 14 | 15 | // figure out how far to increment angle per segment 16 | const dist = distanceBetweenAngles(angleStart, angleEnd); 17 | const angleDelta = dist / steps; 18 | 19 | // initial angle for line 20 | let angle = angleStart + line.rotation; 21 | 22 | // now draw each segment in the arc 23 | for (let i = 0; i < steps; i++) { 24 | // point of the unit circle (-1 to 1) 25 | const x = Math.cos(angle); 26 | const y = Math.sin(angle); 27 | 28 | // use noise to offset the radius of the arc 29 | let frequency = 1.5; 30 | let amplitude = 50; 31 | let noiseSpeed = 0.25; 32 | 33 | // use the (x, y) coordinate to get a noise value 34 | let n = simplex.noise3D(x * frequency, y * frequency, time * noiseSpeed); 35 | 36 | // Try stepping the noise for an interesting result! 37 | // n = n < 0 ? -1 : 1; 38 | 39 | let radiusOffset = n * amplitude; 40 | 41 | // draw an arc with the necessary radius 42 | const computedRadius = radius + radiusOffset; 43 | context.lineTo(x * computedRadius, y * computedRadius); 44 | 45 | // increment angle each segment 46 | angle += angleDelta; 47 | } 48 | 49 | context.stroke(); 50 | }; 51 | -------------------------------------------------------------------------------- /lib/solutions/index1.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: "off" */ 2 | const createLoop = require('raf-loop'); 3 | const createScene = require('./createScene'); 4 | 5 | // get Canvas2D 6 | const canvas = document.querySelector('#canvas'); 7 | const context = canvas.getContext('2d'); 8 | 9 | // pixel density, no higher than 2x for performance 10 | const pixelRatio = Math.min(2, window.devicePixelRatio); 11 | 12 | // an object holding our application state 13 | const app = { 14 | canvas, 15 | context, 16 | pixelRatio, 17 | width: 0, // will be set on resize() 18 | height: 0 19 | }; 20 | 21 | // listen for window resizing 22 | window.addEventListener('resize', resize); 23 | 24 | // set initial sizes before setting up scene & loop 25 | resize(); 26 | 27 | // start a draw loop 28 | createLoop(draw).start(); 29 | 30 | function resize () { 31 | // set up a retina-scaled canvas 32 | app.width = window.innerWidth; 33 | app.height = window.innerHeight; 34 | canvas.width = Math.floor(app.width * pixelRatio); 35 | canvas.height = Math.floor(app.height * pixelRatio); 36 | canvas.style.width = `${app.width}px`; 37 | canvas.style.height = `${app.height}px`; 38 | } 39 | 40 | function draw (dt) { 41 | const { width, height } = app; 42 | 43 | // push context transform state 44 | context.save(); 45 | 46 | // scale everything so it works with pixel ratio 47 | context.scale(pixelRatio, pixelRatio); 48 | 49 | // clear canvas every frame 50 | context.clearRect(0, 0, width, height); 51 | 52 | // draw some shapes! 53 | context.fillRect(20, 30, 25, 40); 54 | 55 | // pop context transform state 56 | context.restore(); 57 | } 58 | -------------------------------------------------------------------------------- /lib/solutions/index2.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: "off" */ 2 | const createLoop = require('raf-loop'); 3 | const createScene = require('./createScene'); 4 | 5 | // get Canvas2D 6 | const canvas = document.querySelector('#canvas'); 7 | const context = canvas.getContext('2d'); 8 | 9 | // pixel density, no higher than 2x for performance 10 | const pixelRatio = Math.min(2, window.devicePixelRatio); 11 | 12 | // an object holding our application state 13 | const app = { 14 | canvas, 15 | context, 16 | pixelRatio, 17 | width: 0, // will be set on resize() 18 | height: 0 19 | }; 20 | 21 | // listen for window resizing 22 | window.addEventListener('resize', resize); 23 | 24 | // set initial sizes before setting up scene & loop 25 | resize(); 26 | 27 | // our virtual "scene" that we will draw 28 | const scene = createScene(app); 29 | 30 | // start a draw loop 31 | createLoop(draw).start(); 32 | 33 | function resize () { 34 | // set up a retina-scaled canvas 35 | app.width = window.innerWidth; 36 | app.height = window.innerHeight; 37 | canvas.width = Math.floor(app.width * pixelRatio); 38 | canvas.height = Math.floor(app.height * pixelRatio); 39 | canvas.style.width = `${app.width}px`; 40 | canvas.style.height = `${app.height}px`; 41 | } 42 | 43 | function draw (dt) { 44 | const { width, height } = app; 45 | 46 | // push context transform state 47 | context.save(); 48 | 49 | // scale everything so it works with pixel ratio 50 | context.scale(pixelRatio, pixelRatio); 51 | 52 | // clear canvas every frame 53 | context.clearRect(0, 0, width, height); 54 | 55 | // draw our "scene" 56 | scene.draw(dt); 57 | 58 | // pop context transform state 59 | context.restore(); 60 | } 61 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | module.exports.computeStepsForArc = computeStepsForArc; 2 | function computeStepsForArc (radius, angleStart = 0, angleEnd = (Math.PI * 2)) { 3 | var dist = Math.abs(angleStart - angleEnd); 4 | if (angleStart > angleEnd) { 5 | dist = 2 * Math.PI - dist; 6 | } 7 | const smoothness = 0.75; 8 | return Math.max(6, Math.floor(Math.pow(radius, smoothness) * dist)); 9 | } 10 | 11 | module.exports.distanceBetweenAngles = distanceBetweenAngles; 12 | function distanceBetweenAngles (angleStart, angleEnd) { 13 | return Math.atan2(Math.sin(angleEnd - angleStart), Math.cos(angleEnd - angleStart)); 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jam3-lesson-canvas2d", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "author": { 8 | "name": "Matt DesLauriers", 9 | "email": "dave.des@gmail.com", 10 | "url": "https://github.com/mattdesl" 11 | }, 12 | "dependencies": { 13 | "raf-loop": "^1.1.3", 14 | "random-float": "^1.0.0", 15 | "simplex-noise": "^2.2.0" 16 | }, 17 | "devDependencies": { 18 | "babel-preset-es2015": "^6.18.0", 19 | "babelify": "^7.3.0", 20 | "budo": "^9.2.2" 21 | }, 22 | "scripts": { 23 | "test": "node test.js", 24 | "start": "budo lib/index.js:bundle.js --dir app --live -- -t babelify" 25 | }, 26 | "keywords": [], 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/Jam3/jam3-lesson-canvas2d.git" 30 | }, 31 | "homepage": "https://github.com/Jam3/jam3-lesson-canvas2d", 32 | "bugs": { 33 | "url": "https://github.com/Jam3/jam3-lesson-canvas2d/issues" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/jam3-lesson-canvas2d/b758c4b466c3f60dd360fbb2cb50c749f9a696c4/screenshots/1.png -------------------------------------------------------------------------------- /screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/jam3-lesson-canvas2d/b758c4b466c3f60dd360fbb2cb50c749f9a696c4/screenshots/2.png -------------------------------------------------------------------------------- /screenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/jam3-lesson-canvas2d/b758c4b466c3f60dd360fbb2cb50c749f9a696c4/screenshots/3.png -------------------------------------------------------------------------------- /screenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/jam3-lesson-canvas2d/b758c4b466c3f60dd360fbb2cb50c749f9a696c4/screenshots/4.png -------------------------------------------------------------------------------- /screenshots/images.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/jam3-lesson-canvas2d/b758c4b466c3f60dd360fbb2cb50c749f9a696c4/screenshots/images.png --------------------------------------------------------------------------------