├── .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
--------------------------------------------------------------------------------