├── .gitignore ├── figures ├── 1.2-cornflower-blue.svg ├── 3.5-polyline-heartbeat.svg ├── 6.4-kinked-curve.svg ├── 1.1-af-klint-inspired.svg ├── 3.9-basic-pattern.svg ├── 8.1-blurred-circle.svg ├── 8.1-blurred-circle - Copy.svg ├── 3.1-albers-illusion.svg ├── 6.3-quadratic-control-point.svg ├── 8.2-ins-outs.svg ├── 6.10-cubic-control-points.svg ├── 3.4-stroke-dasharray-values.svg ├── 8.9-displaced-turbulence.svg ├── 3.10-optical-illusion.svg ├── 6.5-smooth-quadratic.svg ├── 8.10-hubble-bubble.svg ├── 3.3-stroke-linecap-values.svg ├── 6.11-smooth-cubic-bezier.svg ├── 8.3-clipped-filter-region.svg ├── 3.6-polygons-for-the-players.svg ├── 3.2-circle-overlay-loop.svg ├── 8.13-rough-paper.svg ├── 8.14-rocky-randomness.svg ├── 5.2-noise-2d.svg ├── 6.1-two-ls.svg ├── 6.2-zig-zag.svg ├── 9.1-trig-visualised.svg ├── 6.7-elliptical-arc.svg ├── 7.2-making-things-move.svg ├── 4.7-clip-path.svg ├── 3.8-gradients.svg ├── 7.6-circular-loop.svg ├── 8.11-gradient-vs-flat-lighting.svg ├── 8.12-diffuse-vs-specular.svg ├── 7.3-collision-detection.svg ├── 4.14-masking.svg ├── 4.6-palettes.svg ├── 6.8-large-arc-and-sweep.svg ├── 4.15-porto-pareto.svg ├── 8.6-coloured-grid.svg ├── 8.4-full-rgb-grid.svg ├── 6.6-quadratic-slinky.svg ├── 1.3-first-generative-sketch.svg ├── 7.5-radians-and-pi.svg └── 6.12-organic-curves.svg ├── package.json ├── sketches ├── 00-template │ ├── sketch.js │ └── index.html ├── 21-colour-filter │ ├── index.html │ └── sketch.js ├── 14-quadratic-slinky │ ├── index.html │ └── sketch.js ├── 15-generative-arcs │ ├── index.html │ └── sketch.js ├── 09-chance │ ├── index.html │ └── sketch.js ├── 02-basic-shapes │ ├── index.html │ └── sketch.js ├── 10-gaussian │ ├── index.html │ └── sketch.js ├── 11-pareto │ ├── index.html │ └── sketch.js ├── 12-noise-matrix │ ├── index.html │ └── sketch.js ├── 23-rough-paper │ ├── index.html │ └── sketch.js ├── 00-web-safe-spiral │ ├── index.html │ └── sketch.js ├── 07-regular-grids │ ├── index.html │ └── sketch.js ├── 08-colourful-grids │ ├── index.html │ └── sketch.js ├── 13-spinning-noise │ ├── index.html │ └── sketch.js ├── 16-organic-curves │ ├── index.html │ └── sketch.js ├── 17-cursor-tracking │ ├── index.html │ └── sketch.js ├── 20-circular-loop │ ├── index.html │ └── sketch.js ├── 22-hubble-bubble │ ├── index.html │ └── sketch.js ├── 00-trigonometry │ ├── index.html │ └── sketch.js ├── 05-optical-illusion │ ├── index.html │ └── sketch.js ├── 24-rocky-randomness │ ├── index.html │ └── sketch.js ├── 03-circle-overlay-loop │ ├── index.html │ └── sketch.js ├── 18-making-things-move │ ├── index.html │ └── sketch.js ├── 19-collision-detection │ ├── index.html │ └── sketch.js ├── 01-first-generative-sketch │ ├── index.html │ └── sketch.js ├── 06-elements-everywhere │ ├── index.html │ └── sketch.js ├── 04-chalkboard-gag │ ├── index.html │ └── sketch.js ├── 00-adjusting-viewbox │ ├── sketch.js │ └── index.html ├── 00-adjusting-arc │ ├── sketch.js │ └── index.html └── 00-turbulence │ ├── sketch.js │ └── index.html ├── Contributing.md ├── README.md └── LICENSE.txt /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 00-testing/ -------------------------------------------------------------------------------- /figures/1.2-cornflower-blue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /figures/3.5-polyline-heartbeat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /figures/6.4-kinked-curve.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /figures/1.1-af-klint-inspired.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /figures/3.9-basic-pattern.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /figures/8.1-blurred-circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /figures/8.1-blurred-circle - Copy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /figures/3.1-albers-illusion.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /figures/6.3-quadratic-control-point.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /figures/8.2-ins-outs.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /figures/6.10-cubic-control-points.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generative-art-with-javascript-and-svg", 3 | "version": "1.0.0", 4 | "description": "The code to accompany the book Generative Art with JavaScript and SVG, by David Matthew.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/Apress/Generative-Art-with-JavaScript-and-SVG.git" 8 | }, 9 | "author": "David Matthew", 10 | "license": "ISC", 11 | "bugs": { 12 | "url": "https://github.com/Apress/Generative-Art-with-JavaScript-and-SVG/issues" 13 | }, 14 | "homepage": "https://github.com/Apress/Generative-Art-with-JavaScript-and-SVG#readme", 15 | "dependencies": { 16 | "svjs": "^1.0.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sketches/00-template/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Save the root svg as a downloadable file. 16 | document.addEventListener('keydown', (event) => { 17 | let key = event.key.toLowerCase(); 18 | if (key === 's') svg.save(); 19 | }); -------------------------------------------------------------------------------- /figures/3.4-stroke-dasharray-values.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sketches/21-colour-filter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 20 | Colour Filter 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /sketches/14-quadratic-slinky/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 20 | Quadratic Slinky 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /sketches/15-generative-arcs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 20 | Generative Arcs 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Apress Source Code 2 | 3 | Copyright for Apress source code belongs to the author. However, under fair use you are encouraged to fork and contribute minor corrections and updates for the benefit of the author(s) and other readers. 4 | 5 | ## How to Contribute 6 | 7 | 1. Make sure you have a GitHub account. 8 | 2. Fork the repository for the relevant book. 9 | 3. Create a new branch on which to make your change, e.g. 10 | `git checkout -b my_code_contribution` 11 | 4. Commit your change. Include a commit message describing the correction. Please note that if your commit message is not clear, the correction will not be accepted. 12 | 5. Submit a pull request. 13 | 14 | Thank you for your contribution! -------------------------------------------------------------------------------- /figures/8.9-displaced-turbulence.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sketches/00-template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Document 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/09-chance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Gen.chance() 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/02-basic-shapes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Basic Shapes 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/10-gaussian/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Gen.gaussian() 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/11-pareto/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Pareto Distribution 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/12-noise-matrix/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Noise Matrix 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/23-rough-paper/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Rough Paper 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/00-web-safe-spiral/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Web Safe Spiral 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/07-regular-grids/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Regular Grids 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/08-colourful-grids/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Colourful Grids 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/13-spinning-noise/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Spinning Noise 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/16-organic-curves/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Organic Curves 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/17-cursor-tracking/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Cursor Tracking 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/20-circular-loop/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Circular Loop 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/22-hubble-bubble/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Hubble Bubble 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apress Source Code 2 | 3 | This repository accompanies [*Generative Art with JavaScript and SVG*](https://www.link.springer.com/book/10.1007/979-8-8688-0086-3) by David Matthew (Apress, 2024). 4 | 5 | [comment]: #cover 6 | ![Cover image](https://media.springernature.com/w158/springer-static/cover/book/979-8-8688-0086-3.jpg) 7 | 8 | Download the files as a zip using the green button, or clone the repository to your machine using Git. 9 | 10 | ``` 11 | git clone git@github.com:Apress/Generative-Art-with-JavaScript-and-SVG.git 12 | ``` 13 | 14 | ## Releases 15 | 16 | Release v1.0 corresponds to the code in the published book, without corrections or updates. 17 | 18 | ## Contributions 19 | 20 | See the file Contributing.md for more information on how you can contribute to this repository. -------------------------------------------------------------------------------- /sketches/00-trigonometry/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Trigonometry Visualised 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/05-optical-illusion/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Optical Illusion 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/24-rocky-randomness/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Rocky Randomness 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/03-circle-overlay-loop/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Circle Overlay Loop 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/18-making-things-move/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Making Things Move 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/19-collision-detection/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Collision Detection 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/01-first-generative-sketch/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Our First Generative Sketch 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sketches/06-elements-everywhere/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | Elements Everywhere All At Once 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /figures/3.10-optical-illusion.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /figures/6.5-smooth-quadratic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /figures/8.10-hubble-bubble.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sketches/04-chalkboard-gag/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 22 | Chalkboard Gag 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /figures/3.3-stroke-linecap-values.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /figures/6.11-smooth-cubic-bezier.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /sketches/04-chalkboard-gag/sketch.js: -------------------------------------------------------------------------------- 1 | // Import the SvJs library. 2 | import { SvJs } from '../../node_modules/svjs/src/index.js'; 3 | 4 | // Parent SVG. 5 | const svg = new SvJs().addTo(document.getElementById('container')); 6 | 7 | // Viewport and viewBox (1:1 aspect ratio). 8 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 9 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 10 | 11 | // Background. 12 | svg.create('rect').set({ 13 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 14 | }); 15 | 16 | // The line to use for the gag. 17 | let line = '"Bart Bucks" are not legal tender.'; 18 | line = line.toUpperCase(); 19 | 20 | // Run a loop, creating 12 (960 / 80) lines of text. 21 | for (let i = 0; i < 960; i += 80) { 22 | let text = svg.create('text'); 23 | text.content(line); 24 | text.set({ 25 | x: 20, 26 | y: 80 + i, 27 | fill: '#fff', 28 | font_size: 52, 29 | font_family: 'Mynerve' 30 | }); 31 | } 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /figures/8.3-clipped-filter-region.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 12 | 19 | 22 | 23 | 24 | 31 | 37 | 38 | -------------------------------------------------------------------------------- /figures/3.6-polygons-for-the-players.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /figures/3.2-circle-overlay-loop.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /figures/8.13-rough-paper.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sketches/07-regular-grids/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Create our grid group. 16 | let grid = svg.create('g'); 17 | 18 | // Set some grid-related variables. 19 | let gridSize = Gen.random(500, 750); 20 | let rows = Gen.random(3, 10); 21 | let spacing = Gen.random(5, 10); 22 | let increment = gridSize / rows; 23 | let cellSize = Math.abs(increment - spacing); 24 | 25 | // A nested loop to visualise the grid. 26 | for (let y = 0; y < gridSize; y += increment) { 27 | for (let x = 0; x < gridSize; x += increment) { 28 | 29 | // Create a square to frame the cell. 30 | grid.create('rect').set({ 31 | x: x, y: y, width: cellSize, height: cellSize, 32 | fill: 'none', stroke: '#eee', 33 | }); 34 | 35 | } 36 | } 37 | 38 | // Centre the grid within the viewBox. 39 | grid.moveTo(500, 500); 40 | -------------------------------------------------------------------------------- /sketches/02-basic-shapes/sketch.js: -------------------------------------------------------------------------------- 1 | // Import the SvJs library. 2 | import { SvJs } from '../../node_modules/svjs/src/index.js'; 3 | 4 | // Parent SVG. 5 | const svg = new SvJs().addTo(document.getElementById('container')); 6 | 7 | // Viewport and viewBox (1:1 aspect ratio). 8 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 9 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 10 | 11 | // Background. 12 | svg.create('rect').set({ 13 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 14 | }); 15 | 16 | // Main orange square. 17 | svg.create('rect').set({ 18 | x: 150, y: 200, width: 700, height: 600, rx: 15, fill: '#e56411', 19 | stroke: '#fff', stroke_width: 30, paint_order: 'stroke' 20 | }); 21 | 22 | // Blue rectangle. 23 | svg.create('rect').set({ 24 | x: 650, y: 200, width: 200, height: 600, rx: 15, fill: '#69969f' 25 | }); 26 | 27 | // Smaller orange rectangle. 28 | svg.create('rect').set({ 29 | x: 200, y: 425, width: 600, height: 150, rx: 20, fill: '#b84b08' 30 | }); 31 | 32 | // Yellow rectangle. 33 | svg.create('rect').set({ 34 | x: 325, y: 200, width: 175, height: 600, fill: '#fed322' 35 | }); 36 | 37 | // Purple rectangle. 38 | svg.create('rect').set({ 39 | x: 500, y: 200, width: 175, height: 600, fill: '#49283c' 40 | }); 41 | -------------------------------------------------------------------------------- /sketches/03-circle-overlay-loop/sketch.js: -------------------------------------------------------------------------------- 1 | // Import the SvJs library. 2 | import { SvJs } from '../../node_modules/svjs/src/index.js'; 3 | 4 | // Parent SVG. 5 | const svg = new SvJs().addTo(document.getElementById('container')); 6 | 7 | // Viewport and viewBox (1:1 aspect ratio). 8 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 9 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 10 | 11 | // Background. 12 | svg.create('rect').set({ 13 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 14 | }); 15 | 16 | // Circle overlay loop. 17 | for (let i = 1; i <= 6; i += 1) { 18 | 19 | // Vary the radius, and the two vertical centre points. 20 | let r = 50 * i; 21 | let cx = 500; 22 | let cy1 = 800 - r; 23 | let cy2 = 200 + r; 24 | 25 | // Create the blueish circle set. 26 | svg.create('circle').set({ 27 | cx: cx, cy: cy1, r: r, fill: '#99eeff', fill_opacity: 0.1 28 | }); 29 | 30 | // Create the greenish circle set. 31 | svg.create('circle').set({ 32 | cx: cx, cy: cy2, r: r, fill: '#aaffee', fill_opacity: 0.1 33 | }); 34 | } 35 | 36 | // Create a subtle outline to frame the circle sets. 37 | svg.create('circle').set({ 38 | cx: 500, cy: 500, r: 320, fill: 'none', 39 | stroke: '#aaffee', stroke_width: 2, stroke_opacity: 0.1 40 | }); 41 | -------------------------------------------------------------------------------- /sketches/10-gaussian/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | for (let i = 0; i < 10000; i += 1) { 16 | 17 | // Generate x and y co-ordinates with a gaussian distribution. 18 | let gaussianX = Gen.gaussian(500, 150, false); 19 | let gaussianY = Gen.gaussian(500, 150, false); 20 | 21 | // Create the lines based on the gaussian co-ordinates. 22 | svg.create('line').set({ 23 | x1: gaussianX, 24 | y1: gaussianY, 25 | x2: gaussianX + Gen.random(-10, 10), 26 | y2: gaussianY + Gen.random(-10, 10), 27 | stroke: `hsl(${Gen.random(150, 270)} 80% 80% / 0.8)` 28 | }); 29 | } 30 | 31 | // Create a series of circles to frame the distribution. 32 | for (let i = 0; i < 10; i += 1) { 33 | svg.create('circle').set({ 34 | cx: 500, cy: 500, r: 25 + (i * 25), fill: 'none', 35 | stroke: `hsl(0 0% 0% / ${0.25 - (i / 50)})`, stroke_width: 15 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /sketches/00-adjusting-viewbox/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs } from '../../node_modules/svjs/src/index.js'; 2 | 3 | const svg = new SvJs().addTo(document.getElementById('container')); 4 | const minX = document.getElementById('min-x'); 5 | const minY = document.getElementById('min-y'); 6 | const vbwh = document.getElementById('vbwh'); 7 | 8 | svg.set({ 9 | width: '250px', // viewport width 10 | height: '250px', // viewport height 11 | viewBox: '0 0 1000 1000' 12 | }); 13 | 14 | svg.create('rect').set({ 15 | x: 0, y: 0, width: 1000, height: 1000, fill: "#eee" 16 | }); 17 | 18 | svg.create('rect').set({ 19 | x: 200, y: 200, width: 600, height: 600, fill: "crimson", stroke: "gold", stroke_width: 50 20 | }); 21 | 22 | minX.addEventListener('input', () => { 23 | svg.set({ viewBox: `${minX.value} ${minY.value} ${vbwh.value} ${vbwh.value}` }); 24 | document.getElementById('min-x-val').innerHTML = minX.value; 25 | }); 26 | 27 | minY.addEventListener('input', () => { 28 | svg.set({ viewBox: `${minX.value} ${minY.value} ${vbwh.value} ${vbwh.value}` }); 29 | document.getElementById('min-y-val').innerHTML = minY.value; 30 | }); 31 | 32 | vbwh.addEventListener('input', () => { 33 | svg.set({ viewBox: `${minX.value} ${minY.value} ${vbwh.value} ${vbwh.value}` }); 34 | document.getElementById('vbwh-val').innerHTML = vbwh.value; 35 | }); 36 | 37 | -------------------------------------------------------------------------------- /sketches/14-quadratic-slinky/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Style the slinky. 11 | svg.create('style').content(` 12 | #slinky path { 13 | fill: none; 14 | stroke-width: 0.7; 15 | stroke-linecap: round; 16 | }` 17 | ); 18 | 19 | // Background. 20 | svg.create('rect').set({ 21 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 22 | }); 23 | 24 | // Choose a random starting hue. 25 | let hue = Gen.random(0, 360); 26 | 27 | // Set up the slinky path group. 28 | let slinky = svg.create('g').set({ id: 'slinky' }); 29 | 30 | // Start the loop. 31 | for (let i = 0; i < 500; i += 5) { 32 | 33 | // Create the control points. 34 | let cpx = Gen.random(200, 400, false); 35 | let cpy = i - 400; 36 | 37 | // Create the quadratic curve. 38 | slinky.create('path').set({ 39 | stroke: `hsl(${hue} 90% 80% / 0.85)`, 40 | d: `M 0 ${i} q ${cpx} ${cpy} 600 0` 41 | }); 42 | 43 | // Increment the hue. 44 | hue = (hue % 360) + 1.5; 45 | } 46 | 47 | slinky.moveTo(500, 500); 48 | -------------------------------------------------------------------------------- /sketches/00-adjusting-viewbox/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 17 | Adjusting the viewBox 18 | 19 | 20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /sketches/05-optical-illusion/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Create our pattern. 16 | const pattern = svg.createPattern('illusion', 100, 200); 17 | 18 | // Create a white rectangle within the pattern. 19 | // pattern.create('rect').set({ 20 | // x: 5, y: 5, width: 90, height: 190, fill: '#eee' 21 | // }); 22 | 23 | // Create 4 x white squares within the pattern. 24 | for (let i = 0; i < 4; i += 1) { 25 | pattern.create('rect').set({ 26 | x: (i === 3) ? 20: i * 20, 27 | y: i * 50, 28 | width: 50, 29 | height: 50, 30 | fill: '#eee' 31 | }); 32 | } 33 | 34 | // Create 4 x thin grey rectangles to separate the squares. 35 | for (let i = 0; i < 4; i += 1) { 36 | pattern.create('rect').set({ 37 | x: 0, 38 | y: 45 + (i * 50), 39 | width: 100, 40 | height: 5, 41 | fill: '#666' 42 | }); 43 | } 44 | 45 | // Apply our pattern to a rect the size of the viewBox. 46 | svg.create('rect').set({ 47 | x: 0, y: 0, width: 1000, height: 1000, fill: 'url(#illusion)' 48 | }); 49 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Freeware License, some rights reserved 2 | 3 | Copyright (c) 2023 David Matthew 4 | 5 | Permission is hereby granted, free of charge, to anyone obtaining a copy 6 | of this software and associated documentation files (the "Software"), 7 | to work with the Software within the limits of freeware distribution and fair use. 8 | This includes the rights to use, copy, and modify the Software for personal use. 9 | Users are also allowed and encouraged to submit corrections and modifications 10 | to the Software for the benefit of other users. 11 | 12 | It is not allowed to reuse, modify, or redistribute the Software for 13 | commercial use in any way, or for a user’s educational materials such as books 14 | or blog articles without prior permission from the copyright holder. 15 | 16 | The above copyright notice and this permission notice need to be included 17 | in all copies or substantial portions of the software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS OR APRESS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | 28 | -------------------------------------------------------------------------------- /figures/8.14-rocky-randomness.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sketches/13-spinning-noise/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen, Noise } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Noise-related and randomised variables. 16 | let noise = new Noise(); 17 | let nX = 0; 18 | let noiseSpeed = 0.025; 19 | let lines = svg.create('g'); 20 | let hue = Gen.random(0, 360); 21 | let iterations = Gen.random(60, 100); 22 | 23 | // Start the dance. 24 | for (let i = 10; i < iterations; i += 1) { 25 | let noiseValue = noise.get(nX); 26 | let hueShift = Gen.map(noiseValue, -1, 1, -180, 180, false); 27 | let lineLength = Gen.map(noiseValue, -1, 1, 0, 1000, false); 28 | 29 | let l1 = lines.create('line').set({ 30 | x1: 0, y1: 0, x2: 0, y2: lineLength, 31 | stroke: `hsl(${Gen.constrain(hue + hueShift, 0, 360)} 80% 80% / 0.5)`, 32 | stroke_width: 0.5 33 | }); 34 | 35 | let l2 = lines.create('line').set({ 36 | x1: 0, y1: 0, x2: 0, y2: lineLength * 1.1, 37 | stroke: `hsl(${Gen.constrain(hue - hueShift, 0, 360)} 80% 80% / 0.25)`, 38 | stroke_width: 0.5 39 | }); 40 | 41 | l1.rotate(i); 42 | l2.rotate(-i); 43 | 44 | nX += noiseSpeed; 45 | } 46 | 47 | lines.moveTo(500, 500); 48 | lines.rotate(Gen.random(0, 360)); 49 | -------------------------------------------------------------------------------- /sketches/00-adjusting-arc/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Viewport size (1:1 aspect ratio). 4 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 5 | 6 | // Parent SVG. 7 | const svg = new SvJs().addTo(document.getElementById('container')); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Elliptical Arc Curve. 16 | let arc = svg.create('path').set({ 17 | fill: 'none', 18 | stroke: 'purple', 19 | stroke_width: 5, 20 | d: 'M 300 500 A 250 250 0 0 0 700 500' 21 | }); 22 | 23 | // UI controls. 24 | let radiusX = document.getElementById('rx'); 25 | let radiusY = document.getElementById('ry'); 26 | let largeArc = document.getElementById('large-arc'); 27 | let sweep = document.getElementById('sweep'); 28 | 29 | // UI update function. 30 | function updateUI() { 31 | let rx = radiusX.value; 32 | let ry = radiusY.value; 33 | let la = largeArc.checked ? '1' : '0'; 34 | let sw = sweep.checked ? '1' : '0'; 35 | arc.set({ 36 | d: `M 300 500 A ${rx} ${ry} 0 ${la} ${sw} 700 500` 37 | }); 38 | document.getElementById('rx-val').innerText = rx; 39 | document.getElementById('ry-val').innerText = ry; 40 | } 41 | 42 | // Attach event listenters to each input. 43 | radiusX.addEventListener('input', () => { updateUI() }); 44 | radiusY.addEventListener('input', () => { updateUI() }); 45 | largeArc.addEventListener('change', () => { updateUI() }); 46 | sweep.addEventListener('change', () => { updateUI() }); 47 | -------------------------------------------------------------------------------- /sketches/09-chance/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Create our grid group. 16 | let grid = svg.create('g'); 17 | 18 | // Pick a random hue. 19 | let hue = Gen.random(0, 360); 20 | 21 | // Set some grid-related variables. 22 | let gridSize = 660; 23 | let rows = 11; 24 | let spacing = 6; 25 | let increment = gridSize / rows; 26 | let cellSize = Math.abs(increment - spacing); 27 | 28 | // A nested loop to create the grid. 29 | for (let y = 0; y < gridSize; y += increment) { 30 | 31 | // Increment the hue relative to the rows, keeping it within 0 and 360. 32 | hue = (hue >= 360) ? (hue - 360) + (120 / rows) : hue + (120 / rows); 33 | 34 | for (let x = 0; x < gridSize; x += increment) { 35 | 36 | // Run the loop based on chance. 37 | if (Gen.chance(60)) { 38 | for (let i = 0; i < cellSize; i += 1) { 39 | grid.create('line').set({ 40 | x1: Gen.random(x, x + cellSize), 41 | y1: Gen.random(y, y + cellSize), 42 | x2: Gen.random(x, x + cellSize), 43 | y2: Gen.random(y, y + cellSize), 44 | stroke: `hsl(${hue} 80% 80% / 0.33)` 45 | }); 46 | } 47 | } 48 | } 49 | } 50 | 51 | // Centre the grid within the viewBox. 52 | grid.moveTo(500, 500); 53 | -------------------------------------------------------------------------------- /sketches/15-generative-arcs/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Set up a container group for our arc curves. 16 | let arcs = svg.create('g'); 17 | 18 | // Randomise some variables. 19 | let rx = Gen.random(5, 350); 20 | let ry = Gen.random(5, 350); 21 | let hue = Gen.random(0, 360); 22 | 23 | // Create two sets of elliptical arc curves on each iteration. 24 | for (let i = 0; i < 360; i += 1) { 25 | 26 | // Randomise the rotation and large arc flag. 27 | let rotation = Gen.random(0, 180); 28 | let largeArc = Gen.chance() ? 1 : 0; 29 | 30 | // Create a first set of clockwise arc curves (sweep = 1). 31 | arcs.create('path').set({ 32 | fill: 'none', 33 | stroke: `hsl(${hue} 75% 75% / 0.05)`, 34 | d: `M 275 500 A ${rx} ${ry} ${rotation} ${largeArc} 1 725 500` 35 | }); 36 | 37 | // Create a second set of counter-clockwise arc curves (sweep = 0). 38 | arcs.create('path').set({ 39 | fill: 'none', 40 | stroke: `hsl(${hue + 60} 75% 75% / 0.05)`, 41 | d: `M 275 500 A ${rx} ${ry} ${rotation} ${largeArc} 0 725 500` 42 | }); 43 | 44 | // Increment the hue. 45 | hue = (hue % 360) + 0.5; 46 | } 47 | 48 | // Apply a random rotation. 49 | arcs.rotate(Gen.random(0, 360)); 50 | -------------------------------------------------------------------------------- /sketches/22-hubble-bubble/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../../svjs/src/index.js'; 2 | 3 | // Viewport size (1:1 aspect ratio). 4 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 5 | 6 | // Parent SVG. 7 | const svg = new SvJs().addTo(document.getElementById('container')); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Create the source graphic. 16 | svg.create('circle').set({ 17 | cx: 500, 18 | cy: 500, 19 | r: Gen.random(250, 350), 20 | fill: '#000', 21 | filter: 'url(#cosmic)' 22 | }); 23 | 24 | // Initialise the filter. 25 | let filter = svg.createFilter('cosmic'); 26 | 27 | // Create a random amount of turbulence. 28 | filter.create('feTurbulence').set({ 29 | type: 'fractalNoise', 30 | baseFrequency: Gen.random(0.002, 0.006, true), 31 | seed: Gen.random(0, 10000), 32 | numOctaves: 4, 33 | stitchTiles: 'stitch', 34 | result: 'turbulence' 35 | }); 36 | 37 | // Blur the edges of the source graphic. 38 | filter.create('feGaussianBlur').set({ 39 | stdDeviation: Gen.random(10, 25), 40 | in: 'SourceGraphic', 41 | result: 'blurred' 42 | }); 43 | 44 | // Displace the turbulence with the blurred edge of the circle. 45 | filter.create('feDisplacementMap').set({ 46 | in: 'turbulence', 47 | in2: 'blurred', 48 | scale: Gen.random(250, 500), 49 | result: 'distortion' 50 | }); 51 | 52 | // Remove everything beyond the blurred perimeter. 53 | filter.create('feComposite').set({ 54 | in: 'distortion', 55 | in2: 'blurred', 56 | operator: 'atop' 57 | }); 58 | 59 | -------------------------------------------------------------------------------- /figures/5.2-noise-2d.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 22 | 28 | 32 | 33 | 34 | 36 | 37 | 39 | image/svg+xml 40 | 42 | 43 | 44 | 45 | 48 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /figures/6.1-two-ls.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 13 | 15 | 19 | 23 | 24 | 32 | 33 | 40 | 44 | 51 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /sketches/00-web-safe-spiral/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs } from '../../node_modules/svjs/src/index.js'; 2 | 3 | const svgSize = window.innerWidth > window.innerHeight ? window.innerHeight : window.innerWidth; 4 | const bgColor = '#181818'; 5 | 6 | const svg = new SvJs().addTo(document.getElementById('container')); 7 | svg.set({ viewBox: '0 0 1000 1000', width: svgSize, height: svgSize }); 8 | 9 | const rect = svg.create('rect'); 10 | rect.set({ x: 0, y: 0, width: 1000, height: 1000, fill: bgColor }); 11 | 12 | const webSafeColours = createWebSafeColours(); 13 | const spiral = svg.create('g'); 14 | 15 | let angle = 0; 16 | let radius = 5; 17 | let increment = (Math.PI * 12) / webSafeColours.length; 18 | for (let i = 0; i < webSafeColours.length; i += 1) { 19 | angle = increment * i; 20 | let x = Math.cos(angle) * radius; 21 | let y = Math.sin(angle) * radius; 22 | let circle = spiral.create('circle'); 23 | circle.set({ 24 | cx: x, 25 | cy: y, 26 | r: 1 + (i / 20), 27 | transform: 'translate(500, 500)', 28 | fill: webSafeColours[i] 29 | }); 30 | radius += 2; 31 | } 32 | 33 | /** 34 | * Creates an array of the 216 web-safe colours. 35 | * 36 | * @returns {array} The array of colours in hexidecimal string format. 37 | */ 38 | function createWebSafeColours() { 39 | const steps = ['00', '33', '66', '99', 'CC', 'FF']; 40 | const colours = []; 41 | 42 | for (let r = 0; r < steps.length; r += 1) { 43 | for (let g = 0; g < steps.length; g += 1) { 44 | for (let b = 0; b < steps.length; b += 1) { 45 | let colour = `#${steps[r]}${steps[g]}${steps[b]}`; 46 | colours.push(colour); 47 | } 48 | } 49 | } 50 | 51 | return colours; 52 | } 53 | -------------------------------------------------------------------------------- /sketches/12-noise-matrix/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen, Noise } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Set some text styling. 11 | svg.create('style').content(` 12 | text { 13 | font-size: 16px; 14 | font-family: serif; 15 | } 16 | `); 17 | 18 | // Background. 19 | svg.create('rect').set({ 20 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 21 | }); 22 | 23 | // Create our noise, the noise x and y co-ordinates, and noise speed. 24 | let noise = new Noise(); 25 | let nX = 0, nY = 0; 26 | let noiseSpeed = 0.05; 27 | 28 | // Set some grid-related variables. 29 | let noiseGrid = svg.create('g'); 30 | let gridSize = 1000; 31 | let rows = 50; 32 | let increment = gridSize / rows; 33 | 34 | // Create the noise matrix. 35 | for (let x = 0; x < gridSize; x += increment) { 36 | for (let y = 0; y < gridSize; y += increment) { 37 | 38 | // Fetch the noise value. 39 | let noiseValue = noise.get(nX, nY); 40 | 41 | // Map the noise value to a useful range. 42 | noiseValue = Gen.map(noiseValue, -1, 1, 0, 100, false); 43 | 44 | // Create text displaying either 0 or 1 (50% chance). 45 | let text = noiseGrid.create('text'); 46 | text.content(Gen.chance() ? '1' : '0'); 47 | text.set({ 48 | x: x, y: y, fill: `hsl(120 20% ${noiseValue}%)` 49 | }); 50 | 51 | nX += noiseSpeed; 52 | nY += noiseSpeed; 53 | 54 | } 55 | } 56 | 57 | // Centre the grid within the viewBox. 58 | noiseGrid.moveTo(500, 500); 59 | -------------------------------------------------------------------------------- /figures/6.2-zig-zag.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 13 | 15 | 19 | 23 | 24 | 33 | 34 | 42 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /figures/9.1-trig-visualised.svg: -------------------------------------------------------------------------------- 1 | Degrees: 45Radians: 0.79Sine(θ): 0.71Cosine(θ): -0.71 -------------------------------------------------------------------------------- /sketches/17-cursor-tracking/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Randomise some variables. 16 | let hue = Gen.random(0, 360); 17 | let rotation = Gen.random(0, 360); 18 | let iterations = Gen.random(50, 100); 19 | 20 | // This array will allow us to iterate through our ellipses later. 21 | let ellipses = []; 22 | 23 | // Run a loop a random number of times to create the ellipses. 24 | for (let i = 0; i < iterations; i += 1) { 25 | 26 | // Create our ellipse. 27 | let ellipse = svg.create('ellipse'); 28 | ellipse.set({ 29 | cx: 500, 30 | cy: 500, 31 | rx: 100 + (i * 3), 32 | ry: 300 + (i * 2), 33 | fill: 'none', 34 | stroke: `hsl(${hue} 80% 80% / 0.6)`, 35 | transform: `rotate(${rotation + (i * 2)} 500 500)` 36 | }); 37 | 38 | // Add the ellipse to the array. 39 | ellipses.push(ellipse); 40 | 41 | // Increment the hue. 42 | hue = (hue % 360) + 2; 43 | } 44 | 45 | // Adjust the centre point of each ellipse relative to our cursor. 46 | svg.trackCursor(() => { 47 | ellipses.forEach((ellipse) => { 48 | ellipse.set({ 49 | cx: svg.cursorX, 50 | cy: svg.cursorY 51 | }); 52 | }); 53 | }); 54 | 55 | // Save the root svg as a downloadable file. 56 | document.addEventListener('keydown', (event) => { 57 | let key = event.key.toLowerCase(); 58 | if (key === 's') svg.save(); 59 | }); -------------------------------------------------------------------------------- /figures/6.7-elliptical-arc.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 22 | 24 | 32 | 35 | 40 | 46 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /sketches/00-adjusting-arc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 30 | Adjusting an Elliptical Arc Curve 31 | 32 | 33 | 34 | 35 |
36 |
37 |
38 | 39 | 40 | 250 41 |
42 |
43 | 44 | 45 | 250 46 |
47 |
48 | 49 | 50 |
51 |
52 | 53 | 54 |
55 |
56 | 57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /sketches/00-turbulence/sketch.js: -------------------------------------------------------------------------------- 1 | // Import the SvJs library. 2 | import { SvJs } from '../../node_modules/svjs/src/index.js'; 3 | 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | const types = document.getElementsByName('type'); 6 | const tiles = document.getElementsByName('stitchTiles'); 7 | const freq = document.getElementById('freq'); 8 | const octs = document.getElementById('octaves'); 9 | const seed = document.getElementById('seed'); 10 | 11 | svg.set({ width: '250px', height: '250px', viewBox: '0 0 1000 1000' }); 12 | 13 | svg.create('rect').set({ x: 0, y: 0, width: 1000, height: 1000, fill: "#181818" }); 14 | 15 | let filter = svg.createFilter('noise'); 16 | 17 | let turbulence = filter.create('feTurbulence').set({ 18 | type: 'turbulence', 19 | baseFrequency: 0.01, 20 | seed: 1, 21 | numOctaves: 1, 22 | stitchTiles: 'stitch', 23 | result: 'turbulence' 24 | }); 25 | 26 | filter.create('feComposite').set({ 27 | in: 'turbulence', 28 | in2: 'SourceGraphic', 29 | operator: 'atop', 30 | result: 'atop' 31 | }); 32 | 33 | svg.create('rect').set({ 34 | x: 150, 35 | y: 150, 36 | width: 700, 37 | height: 700, 38 | fill: "#fff", 39 | rx: 20, 40 | ry: 20, 41 | filter: 'url(#noise)' 42 | }); 43 | 44 | types.forEach((type) => { 45 | type.addEventListener('change', () => { 46 | if (type.checked) turbulence.set({ type: type.value }); 47 | }); 48 | }); 49 | 50 | freq.addEventListener('input', () => { 51 | turbulence.set({ baseFrequency: freq.value }); 52 | document.getElementById('freq-val').innerHTML = freq.value; 53 | }); 54 | 55 | octs.addEventListener('input', () => { 56 | turbulence.set({ numOctaves: octs.value }); 57 | document.getElementById('octaves-val').innerHTML = octs.value; 58 | }); 59 | 60 | seed.addEventListener('input', () => { 61 | turbulence.set({ seed: seed.value }); 62 | document.getElementById('seed-val').innerHTML = seed.value; 63 | }); 64 | 65 | tiles.forEach((tile) => { 66 | tile.addEventListener('change', () => { 67 | if (tile.checked) turbulence.set({ stitchTiles: tile.value }); 68 | }); 69 | }); 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /sketches/23-rough-paper/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../../svjs/src/index.js'; 2 | 3 | // Viewport size (1:1 aspect ratio). 4 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 5 | 6 | // Parent SVG. 7 | const svg = new SvJs().addTo(document.getElementById('container')); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Create a parchment-coloured gradient. 16 | svg.createGradient('parchment', 'linear', ['#fffbeb', '#fde68a'], 90); 17 | 18 | // Create the path for the ripped paper. 19 | let paper = svg.create('path').set({ 20 | fill: 'url(#parchment)', 21 | stroke: '#4444', 22 | d: 'M 0,0 h 175 l 175,550 h -350 Z M 210,0 h 340 v 550 h -165 Z', 23 | filter: 'url(#rough-paper)' 24 | }); 25 | 26 | // Centre it. 27 | paper.moveTo(500, 500); 28 | 29 | // Initialise the filter. 30 | let filter = svg.createFilter('rough-paper'); 31 | 32 | // Add turbulence to simulate the paper grain. 33 | filter.create('feTurbulence').set({ 34 | type: 'fractalNoise', 35 | numOctaves: 5, 36 | baseFrequency: 0.04, 37 | seed: Gen.random(0, 100), 38 | result: 'turbulence' 39 | }); 40 | 41 | // Shine diffuse lighting on the turbulence. 42 | filter.create('feDiffuseLighting').set({ 43 | surfaceScale: 1, 44 | diffuseConstant: 1.3, 45 | in: 'turbulence', 46 | result: 'lighting' 47 | }).create('feDistantLight').set({ 48 | azimuth: 180, 49 | elevation: 45, 50 | }); 51 | 52 | // Distort the paper source graphic with turbulence. 53 | filter.create('feDisplacementMap').set({ 54 | in: 'SourceGraphic', 55 | in2: 'turbulence', 56 | scale: 25, 57 | result: 'distortion' 58 | }); 59 | 60 | // Merge the lighting with the rough-edged paper. 61 | filter.create('feComposite').set({ 62 | in: 'lighting', 63 | in2: 'distortion', 64 | operator: 'in', 65 | result: 'composite' 66 | }); 67 | 68 | // Re-introduce the parchment gradient. 69 | filter.create('feBlend').set({ 70 | in: 'composite', 71 | in2: 'distortion', 72 | mode: 'multiply' 73 | }); 74 | -------------------------------------------------------------------------------- /sketches/16-organic-curves/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen, Noise } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Noise-related. 16 | let noise = new Noise(); 17 | let n = Gen.random(0, 1000); 18 | let speed = 0.05; 19 | let amplifier = Gen.random(200, 500); 20 | 21 | // Curve and colour-related. 22 | let curves = svg.create('g'); 23 | let numCurves = Gen.random(75, 125); 24 | let hue = Gen.random(0, 360); 25 | 26 | for (let i = 0; i < numCurves; i += 1) { 27 | 28 | // Retrieve and re-map our noise value. 29 | let noiseValue = noise.get(n); 30 | noiseValue = Gen.map(noiseValue, -1, 1, -amplifier, amplifier, false); 31 | 32 | // M command co-ordinates. 33 | let mx = 0; 34 | let my = 0 + (i * 5); 35 | 36 | // C command co-ordinates. 37 | let cpx1 = 0 + noiseValue; 38 | let cpy1 = -100; 39 | let cpx2 = 250 + noiseValue; 40 | let cpy2 = -100; 41 | let x2 = 300; 42 | let y2 = 0; 43 | 44 | // S command co-ordinates. 45 | let spx = 350 + noiseValue; 46 | let spy = 100; 47 | let x3 = 300; 48 | let y3 = -50; 49 | 50 | // Create the organic curve. 51 | curves.create('path').set({ 52 | fill: 'none', 53 | stroke: `hsl(${hue} 80% 80% / 0.8)`, 54 | d: `M ${[mx, my]} c ${[cpx1, cpy1, cpx2, cpy2, x2, y2]} s ${[spx, spy, x3, y3]}` 55 | }); 56 | 57 | // Increment the noise and hue. 58 | n += speed; 59 | hue = (hue % 360) + (noiseValue / 25); 60 | 61 | // 10% chance of spawning a 'bubble'. 62 | if (Gen.chance(10)) { 63 | svg.create('circle').set({ 64 | r: Gen.random(5, 50), 65 | cx: Gen.random(150, 850), 66 | cy: Gen.random(150, 850), 67 | fill: `hsl(0 0% 100% / 0.1)`, 68 | stroke: '#888' 69 | }); 70 | } 71 | } 72 | 73 | curves.moveTo(500, 500); 74 | -------------------------------------------------------------------------------- /sketches/00-turbulence/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 22 | Tweaking Turbulence 23 | 24 | 25 | 26 | 27 |
28 | 29 |
30 | 31 |
32 | 33 | 34 | 35 | 36 |
37 | 38 |
39 | 40 | 41 |
42 | 43 |
44 | 45 | 46 |
47 | 48 |
49 | 50 | 51 |
52 | 53 |
54 | 55 | 56 | 57 | 58 |
59 | 60 |
61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /sketches/06-elements-everywhere/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Set a random iteration count for the loop. 16 | let iterations = Gen.random(250, 500, false); 17 | 18 | // Our shapes array. 19 | let elements = ['circle', 'line', 'rect']; 20 | 21 | // Start the loop. 22 | for (let i = 0; i < iterations; i += 1) { 23 | 24 | // Pick a random element. 25 | let element = Gen.random(elements); 26 | 27 | // Set up variables that we can use on any element. 28 | let x = Gen.random(200, 800); 29 | let y = Gen.random(200, 800); 30 | let fill = `hsl(${Gen.random(120, 240)} 80% 80% / ${Gen.random(5, 40)}%)`; 31 | let stroke = `hsl(${Gen.random(0, 120)} 80% 80% / ${Gen.random(5, 40)}%)`; 32 | let strokeWidth = `${Gen.random(1, 3)}`; 33 | 34 | // Initialise the properties variable. 35 | let props; 36 | 37 | // Populate the properties depending on the element chosen. 38 | switch(element) { 39 | case 'circle': 40 | props = { 41 | cx: x, 42 | cy: y, 43 | r: Gen.random(1, 10), 44 | fill: fill, 45 | stroke: stroke, 46 | stroke_width: strokeWidth 47 | }; 48 | break; 49 | case 'line': 50 | props = { 51 | x1: x, 52 | y1: y, 53 | x2: x + (Gen.random(-25, 25)), 54 | y2: y + (Gen.random(-25, 25)), 55 | stroke: stroke 56 | }; 57 | break; 58 | case 'rect': 59 | props = { 60 | x: x, 61 | y: y, 62 | width: Gen.random(5, 25), 63 | height: Gen.random(5, 25), 64 | fill: fill, 65 | stroke: stroke, 66 | stroke_width: strokeWidth, 67 | transform: `rotate(${Gen.random(0, 360)} 500 500)` 68 | } 69 | } 70 | 71 | // Create the element and set its properties. 72 | svg.create(element).set(props); 73 | } 74 | -------------------------------------------------------------------------------- /sketches/21-colour-filter/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../../svjs/src/index.js'; 2 | 3 | // Viewport size (1:1 aspect ratio). 4 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 5 | 6 | // Parent SVG. 7 | const svg = new SvJs().addTo(document.getElementById('container')); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Create our grid group. 16 | let grid = svg.create('g'); 17 | 18 | // Set some grid-related variables. 19 | let gridSize = Gen.random(500, 750); 20 | let rows = Gen.random(3, 10); 21 | let spacing = Gen.random(5, 10); 22 | let increment = gridSize / rows; 23 | let cellSize = Math.abs(increment - spacing); 24 | 25 | // A nested loop to visualise the grid. 26 | for (let y = 0; y < gridSize; y += increment) { 27 | for (let x = 0; x < gridSize; x += increment) { 28 | 29 | if (Gen.chance(55)) { 30 | grid.create('rect').set({ 31 | x: x, y: y, width: cellSize, height: cellSize, 32 | fill: `rgb( 33 | ${[Gen.random(0, 255), Gen.random(0, 255), Gen.random(0, 255)]} 34 | )` 35 | }); 36 | } 37 | } 38 | } 39 | 40 | // Centre the grid within the viewBox. 41 | grid.moveTo(500, 500); 42 | 43 | // Set up a blend modes array. 44 | let blendModes = ['screen', 'overlay', 'lighten', 'color-dodge', 'soft-light', 'color']; 45 | 46 | // Initialise the filter. 47 | let filter = svg.createFilter('colourise'); 48 | 49 | // De-saturate the input. 50 | filter.create('feColorMatrix').set({ 51 | type: 'saturate', 52 | values: 0, 53 | result: 'desaturate' 54 | }); 55 | 56 | // Set a flood colour. 57 | filter.create('feFlood').set({ 58 | flood_color: '#7F462C', 59 | result: 'flood' 60 | }); 61 | 62 | // Randomise the blend mode. 63 | filter.create('feBlend').set({ 64 | mode: Gen.random(blendModes), 65 | in: 'flood', 66 | in2: 'desaturate', 67 | result: 'blend' 68 | }); 69 | 70 | // Composite the blend 'atop' the original. 71 | filter.create('feComposite').set({ 72 | in: 'blend', 73 | in2: 'SourceGraphic', 74 | operator: 'atop' 75 | }); 76 | 77 | // Apply the filter. 78 | grid.set({ filter: 'url(#colourise)' }); 79 | -------------------------------------------------------------------------------- /sketches/01-first-generative-sketch/sketch.js: -------------------------------------------------------------------------------- 1 | // Import the SvJs library. 2 | import { SvJs } from '../../node_modules/svjs/src/index.js'; 3 | 4 | // Create some global variables. 5 | const svgSize = window.innerWidth > window.innerHeight ? window.innerHeight : window.innerWidth; 6 | const bgColor = '#181818'; 7 | 8 | // Create an object to store some of our randomised parameters. 9 | const randomised = { 10 | hue: random(0, 360), 11 | rotation: random(-180, 180), 12 | iterations: random(10, 100) 13 | } 14 | 15 | // Create our parent SVG element and attach it to the element with id 'container'. 16 | const svg = new SvJs(); 17 | svg.addTo(document.getElementById('container')); 18 | 19 | // Set the width and height of the viewBox (the units we work in) and the displayed size of the SVG. 20 | svg.set({ viewBox: '0 0 1000 1000', width: svgSize, height: svgSize }); 21 | 22 | // Create a background layer - a rectangle the full size of our viewBox. 23 | const rect = svg.create('rect'); 24 | rect.set({ x: 0, y: 0, width: 1000, height: 1000, fill: bgColor }); 25 | 26 | // Run a loop a random number of times to create our ellipses. 27 | for (let i = 0; i < randomised.iterations; i += 1) { 28 | 29 | // Calculate the center point, the x and y radii of our ellipse, and its rotation. 30 | let center = 500; 31 | let radiusX = 100 + (i * 3); 32 | let radiusY = 300 + (i * 2); 33 | let rotation = randomised.rotation + (i * 2); 34 | 35 | // If our random hue is less than 180, increment it. Otherwise decrement it. 36 | let hue; 37 | if (randomised.hue < 180) { 38 | hue = randomised.hue + (i * 3); 39 | } else { 40 | hue = randomised.hue - (i * 3); 41 | } 42 | 43 | // Create our ellipse. 44 | let ellipse = svg.create('ellipse'); 45 | ellipse.set({ 46 | cx: center, 47 | cy: center, 48 | rx: radiusX, 49 | ry: radiusY, 50 | fill: 'none', 51 | stroke: `hsl(${hue} 80% 80% / 0.6)`, 52 | transform: `rotate(${rotation} ${center} ${center})` 53 | }); 54 | } 55 | 56 | /** 57 | * Gets a random number between a minimum and maximum value. 58 | */ 59 | function random(min, max, integer = true) { 60 | let random = Math.random() * (max - min) + min; 61 | let number = integer ? Math.floor(random) : random; 62 | return number; 63 | } 64 | -------------------------------------------------------------------------------- /figures/7.2-making-things-move.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 19 | 26 | 27 | 29 | 39 | 49 | 59 | 69 | 70 | 72 | 73 | 75 | 76 | 77 | David Matthew 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /sketches/11-pareto/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Create a group for our generative city. 16 | let portoPareto = svg.create('g'); 17 | 18 | // Create the sky gradient. 19 | svg.createGradient('sky', 'linear', ['#f58b10', '#d21263', '#940c5e', '#25226c'], 90); 20 | 21 | // Create the sky and apply the gradient. 22 | portoPareto.create('rect').set({ 23 | x: 150, y: 150, width: 700, height: 400, fill: 'url(#sky)' 24 | }); 25 | 26 | // Create the river gradient. 27 | svg.createGradient('river', 'linear', ['#80e5ff10', '#70b566'], 90); 28 | 29 | // Create the river and apply the gradient. 30 | portoPareto.create('rect').set({ 31 | x: 150, y: 555, width: 700, height: 295, fill: 'url(#river)' 32 | }); 33 | 34 | // A loop for our generative cityscape. 35 | for (let i = 0; i < 60; i += 1) { 36 | 37 | // Get a pareto distribution with a min height of 20. 38 | let pareto = Gen.pareto(20); 39 | 40 | // Constrain the height, and slightly randomise the upper limit. 41 | let height = Gen.constrain(pareto, 20, Gen.random(150, 200)); 42 | 43 | // Create our buildings. 44 | portoPareto.create('line').set({ 45 | x1: 150 + (i * 12), y1: 550, 46 | x2: 150 + (i * 12), y2: 550 - height, 47 | stroke: '#181818', stroke_width: 8 48 | }); 49 | } 50 | 51 | // Create a radial gradient. 52 | svg.createGradient('radialGrad', 'radial', ['#ffffff', '#ffffff60']); 53 | 54 | // Create a mask, and inside it create the circle with the radial gradient. 55 | let mask = svg.create('mask').set({ id: 'mask' }); 56 | mask.create('circle').set({ 57 | cx: 500, cy: 500, r: 325, fill: 'url(#radialGrad)', 58 | }); 59 | 60 | // Apply the mask to the group. 61 | portoPareto.set({ mask: 'url(#mask)' }); 62 | 63 | // Create a linear gradient for our circular frame. 64 | svg.createGradient('strokeGrad', 'linear', ['#eeeeee', '#eeeeee15']); 65 | 66 | // Create the frame and apply the gradient. 67 | svg.create('circle').set({ 68 | cx: 500, cy: 500, r: 345, fill: 'none', stroke: 'url(#strokeGrad)', stroke_width: 2.5 69 | }); -------------------------------------------------------------------------------- /sketches/20-circular-loop/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Randomise some variables. 16 | let numCircles = 35;//Gen.random(20, 35); 17 | let baseRadius = Gen.random(5, 25, true); 18 | let hue = Gen.random(0, 360); 19 | 20 | let animations = []; 21 | let isPaused = false; 22 | 23 | // Arrange and animate the circles. 24 | for (let i = 0; i < numCircles; i += 1) { 25 | 26 | // Create the circle, but don't set the position yet. 27 | let circle = svg.create('circle').set({ 28 | r: baseRadius, cx: 500, cy: 500, 29 | fill: 'none', 30 | stroke: `hsl(${hue} 80% 80% / 0.75)`, 31 | transform_origin: '500 500' 32 | }); 33 | 34 | // Calculate the current angle. 35 | let angle = Math.PI * 2 / numCircles * i; 36 | 37 | // Get the sine and cosine of the angle. 38 | let sin = Math.sin(angle); 39 | let cos = Math.cos(angle); 40 | 41 | // Map the sine and cosine to the desired range. 42 | let cx = Gen.map(sin, -1, 1, 150, 850, false); 43 | let cy = Gen.map(cos, -1, 1, 150, 850, false); 44 | 45 | // Set the initial and target radii. 46 | let r1 = baseRadius * 2 + (i * 10); 47 | let r2 = baseRadius / (i + 10); 48 | 49 | // Move from (500, 500) to (cx, cy), reduce the radius, rotate. 50 | animations.push(circle.element.animate({ 51 | cx: [500, cx, 500], 52 | cy: [500, cy, 500], 53 | r: [r1, r2, r1], 54 | transform: ['rotate(0deg)', 'rotate(360deg)'] 55 | }, { 56 | duration: 10000, 57 | iterations: Infinity, 58 | easing: ['ease-in-out'] 59 | })); 60 | 61 | // Increment the hue. 62 | hue = (hue % 360) + (180 / numCircles); 63 | } 64 | 65 | // Save the root svg as a downloadable file. 66 | document.addEventListener('keydown', (event) => { 67 | let key = event.key.toLowerCase(); 68 | if (key === 's') svg.save(); 69 | }); 70 | 71 | 72 | document.addEventListener('keydown', (event) => { 73 | let key = event.key.toLowerCase(); 74 | if (key === 'p') { 75 | animations.forEach((animation) => { 76 | if (isPaused) { 77 | animation.pause(); 78 | } else { 79 | animation.play(); 80 | } 81 | }); 82 | isPaused = !isPaused; 83 | } 84 | }); 85 | 86 | -------------------------------------------------------------------------------- /sketches/08-colourful-grids/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Create our grid group. 16 | let grid = svg.create('g'); 17 | 18 | // Create our colour palettes. 19 | let palettes = [ 20 | ['#5465FF', '#788BFF', '#9BB1FF', '#BFD7FF', '#E2FDFF'], 21 | ['#22577A', '#38A3A5', '#57CC99', '#80ED99', '#C7f9CC'], 22 | ['#4C5760', '#93A8AC', '#D7CEB2', '#A59E8C', '#66635B'] 23 | ]; 24 | 25 | // Pick a random palette. 26 | let pickedPalette = Gen.random(palettes); 27 | 28 | // Set some grid-related variables. 29 | let gridSize = 600; 30 | let rows = Gen.random(3, 10); 31 | let spacing = 10; 32 | let increment = gridSize / rows; 33 | let cellSize = Math.abs(increment - spacing); 34 | 35 | // A nested loop to create the grid. 36 | for (let y = 0; y < gridSize; y += increment) { 37 | for (let x = 0; x < gridSize; x += increment) { 38 | 39 | // Create our clip path with a unique id. 40 | let clip = svg.create('clipPath').set({ id: `${x}${y}` }); 41 | 42 | // Create the clip path shape. 43 | clip.create('rect').set({ 44 | x: x, y: y, width: cellSize, height: cellSize 45 | }); 46 | 47 | // Define our possible positions. 48 | let positions = [ 49 | [x, y], // top left 50 | [x + cellSize, y], // top right 51 | [x + cellSize, y + cellSize], // bottom right 52 | [x, y + cellSize] // bottom left 53 | ]; 54 | 55 | // Pick a random position. 56 | let pickedPosition = Gen.random(positions); 57 | 58 | // Create a group for our circles. 59 | let circles = grid.create('g'); 60 | 61 | // Create the circles, applying the picked position and palette. 62 | for (let i = 0; i < 5; i += 1) { 63 | circles.create('circle').set({ 64 | cx: pickedPosition[0], 65 | cy: pickedPosition[1], 66 | r: cellSize - (i * (cellSize / 5)), 67 | fill: pickedPalette[i] 68 | }); 69 | } 70 | 71 | // Apply the clip path to the circle group. 72 | circles.set({ 73 | clip_path: `url(#${clip.get('id')})` 74 | }); 75 | 76 | // Create a square to frame the cell. 77 | grid.create('rect').set({ 78 | x: x, y: y, width: cellSize, height: cellSize, 79 | fill: 'none', stroke: '#eee', 80 | }); 81 | 82 | } 83 | } 84 | 85 | // Centre the grid within the viewBox. 86 | grid.moveTo(500, 500); 87 | -------------------------------------------------------------------------------- /sketches/24-rocky-randomness/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../../svjs/src/index.js'; 2 | 3 | // Viewport size (1:1 aspect ratio). 4 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 5 | 6 | // Parent SVG. 7 | const svg = new SvJs().addTo(document.getElementById('container')); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Create our source graphic. 16 | svg.create('circle').set({ 17 | r: 300, 18 | cx: 500, 19 | cy: 500, 20 | fill: 'url(#random-gradient)', 21 | filter: 'url(#rocky-randomness)' 22 | }); 23 | 24 | // A random colour array. 25 | let colours = [ 26 | `hsl(${Gen.random(0, 360)} 80% 80% / 0.75)`, 27 | `hsl(${Gen.random(0, 360)} 80% 80% / 0.75)`, 28 | `hsl(${Gen.random(0, 360)} 80% 80% / 0.75)` 29 | ]; 30 | 31 | // A gradient with a randomised rotation and array of colours. 32 | svg.createGradient('random-gradient', 'linear', colours, Gen.random(0, 360)); 33 | 34 | // Initalise the filter. 35 | let filter = svg.createFilter('rocky-randomness'); 36 | 37 | // Create the primary turbulence. 38 | filter.create('feTurbulence').set({ 39 | type: 'turbulence', 40 | numOctaves: Gen.random(2, 7), 41 | baseFrequency: Gen.random(0.003, 0.01, true), 42 | seed: Gen.random(0, 1000), 43 | result: 'turbulence' 44 | }); 45 | 46 | // Set up another instance of turbulence. 47 | filter.create('feTurbulence').set({ 48 | type: 'fractalNoise', 49 | numOctaves: Gen.random(3, 7), 50 | baseFrequency: Gen.random(0.01, 0.07, true), 51 | seed: Gen.random(0, 1000), 52 | result: 'noise' 53 | }); 54 | 55 | // Distort the first instance of turbulence with the second. 56 | filter.create('feDisplacementMap').set({ 57 | in: 'turbulence', 58 | in2: 'noise', 59 | scale: Gen.random(25, 50), 60 | xChannelSelector: 'R', 61 | yChannelSelector: 'G', 62 | result: 'distortion' 63 | }); 64 | 65 | // Shine a specular point light on the distorted output. 66 | filter.create('feSpecularLighting').set({ 67 | in: 'distortion', 68 | surfaceScale: Gen.random(5, 30), 69 | specularConstant: Gen.random(2, 6), 70 | specularExponent: Gen.random(10, 25), 71 | result: 'lighting' 72 | }).create('fePointLight').set({ 73 | x: Gen.random([-50, 500, 1050]), 74 | y: Gen.random([-50, 1050]), 75 | z: Gen.random(100, 300) 76 | }); 77 | 78 | // Blur the source graphic. 79 | filter.create('feGaussianBlur').set({ 80 | in: 'SourceGraphic', 81 | stdDeviation: Gen.random(25, 50), 82 | result: 'blur' 83 | }); 84 | 85 | // Bring the lit texture in via the blurred source graphic. 86 | filter.create('feComposite').set({ 87 | in: 'lighting', 88 | in2: 'blur', 89 | operator: 'in', 90 | result: 'comp1' 91 | }); 92 | 93 | // Recover the original gradient. 94 | filter.create('feComposite').set({ 95 | in: 'blur', 96 | in2: 'comp1', 97 | operator: 'atop' 98 | }); 99 | -------------------------------------------------------------------------------- /figures/4.7-clip-path.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 25 | 30 | 31 | 34 | 41 | 42 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 61 | 67 | 74 | 82 | 86 | 90 | 91 | -------------------------------------------------------------------------------- /sketches/19-collision-detection/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Create a frame to act as the boundary. 16 | let frameSize = Gen.random(350, 700); 17 | let frame = svg.create('rect').set({ 18 | x: (1000 - frameSize) / 2, 19 | y: (1000 - frameSize) / 2, 20 | width: frameSize, 21 | height: frameSize, 22 | fill: '#252525', 23 | stroke: `hsl(${Gen.random(0, 360)} 80% 80% / 0.25)`, 24 | stroke_width: frameSize / 10, 25 | }); 26 | 27 | // Randomise the number of circles and set up an empty array. 28 | let numCircles = Math.floor(frameSize / 20); 29 | let circles = []; 30 | 31 | // Populate this array. 32 | for (let i = 0; i < numCircles; i += 1) { 33 | 34 | // Randomise the radius relative to the frame size. 35 | let radius = Gen.random(frameSize / 100, frameSize / 25); 36 | 37 | // Create variables to control the speed on the x and y axes. 38 | let velocityX = Gen.random(0.1, 5, true); 39 | let velocityY = Gen.random(0.1, 5, true); 40 | 41 | // Apply the above variables and randomise the hue. 42 | let circle = svg.create('circle').set({ 43 | cx: 500, 44 | cy: 500, 45 | r: radius, 46 | vx: Gen.chance() ? velocityX : -velocityX, 47 | vy: Gen.chance() ? velocityY : -velocityY, 48 | fill: `hsl(${Gen.random(0, 360)} 80% 80% / 0.5)` 49 | }); 50 | 51 | // Store the circle in the array. 52 | circles.push(circle); 53 | } 54 | 55 | // Get the frame start point (x or y) and the inset. 56 | let frameEdge = Number(frame.get('x')); 57 | let frameInset = Number(frame.get('stroke-width')) / 2; 58 | 59 | // The animation loop. 60 | function animate() { 61 | 62 | // Check collisions for each circle. 63 | circles.forEach((circle) => { 64 | 65 | // Calculate the lower and upper bounds for each circle. 66 | let radius = Number(circle.get('r')); 67 | let lowerBound = frameEdge + radius + frameInset; 68 | let upperBound = frameEdge + frameSize - radius - frameInset; 69 | 70 | // Retrieve the position and velocity. 71 | let cx = Number(circle.get('cx')); 72 | let cy = Number(circle.get('cy')); 73 | let vx = Number(circle.get('vx')); 74 | let vy = Number(circle.get('vy')); 75 | 76 | // Check for collisions, and if found reverse the polarity. 77 | if (cx <= lowerBound || cx >= upperBound) vx = -vx; 78 | if (cy <= lowerBound || cy >= upperBound) vy = -vy; 79 | 80 | // Update the position. 81 | cx += vx; 82 | cy += vy; 83 | 84 | // Set the new values. 85 | circle.set({ cx: cx, cy: cy, vx: vx, vy: vy }); 86 | }); 87 | 88 | // The recursive bit. 89 | requestAnimationFrame(animate); 90 | } 91 | 92 | // Call the animation. 93 | animate(); 94 | 95 | // Save the root svg as a downloadable file. 96 | document.addEventListener('keydown', (event) => { 97 | let key = event.key.toLowerCase(); 98 | if (key === 's') svg.save(); 99 | }); -------------------------------------------------------------------------------- /sketches/00-trigonometry/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs, Gen } from '../../node_modules/svjs/src/index.js'; 2 | 3 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 4 | 5 | const svg = new SvJs().addTo(document.getElementById('container')); 6 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 7 | 8 | svg.create('style').content(` 9 | path { 10 | stroke-width: 2 11 | } 12 | 13 | text { 14 | fill: #aaa; 15 | font-family: Arial; 16 | font-size: 20px; 17 | letter-spacing: 1.5px; 18 | } 19 | 20 | .point { 21 | fill: #99f6e4; 22 | stroke: #99f6e425; 23 | stroke-width: 25 24 | } 25 | `); 26 | 27 | svg.create('rect').set({ 28 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 29 | }); 30 | 31 | svg.create('path').set({ 32 | fill: 'none', 33 | stroke: '#333', 34 | stroke_width: 1, 35 | d: 'M 500,150 v 600 M 200,450 h 600' 36 | }); 37 | 38 | svg.createGradient('grad', 'linear', ['#fde047', '#a855f7'], 45); 39 | 40 | svg.createFilter('blur').create('feGaussianBlur').set({ stdDeviation: 15 }); 41 | 42 | svg.create('circle').set({ 43 | cx: 500, cy: 450, r: 300, fill: 'none', stroke: 'url(#grad)', stroke_width: 5 44 | }); 45 | 46 | svg.create('circle').set({ 47 | cx: 500, cy: 450, r: 325, fill: 'none', 48 | stroke: 'url(#grad)', stroke_width: 5, filter: 'url(#blur)' 49 | }); 50 | 51 | let hyp = svg.create('path').set({ stroke: '#5991db', d: 'M 500,450 L 800,450' }); 52 | let opp = svg.create('path').set({ stroke: '#fde047', d: 'M 500,450 L 800,450' }); 53 | let adj = svg.create('path').set({ stroke: '#ec4899', d: 'M 500,450 L 800,450' }); 54 | 55 | svg.create('circle').set({ class: 'point', cx: 500, cy: 450, r: 10 }); 56 | let hypCircle = svg.create('circle').set({ class: 'point', r: 10, cx: 800, cy: 450 }); 57 | let oppCircle = svg.create('circle').set({ class: 'point', r: 10, cx: 800, cy: 450 }); 58 | 59 | let degreesTxt = svg.create('text').set({ x: 350, y: 880 }) 60 | degreesTxt.content('Degrees: 360'); 61 | let radiansTxt = svg.create('text').set({ x: 350, y: 910 }) 62 | radiansTxt.content('Radians: 6.28'); 63 | let sineTxt = svg.create('text').set({ x: 510, y: 880 }) 64 | sineTxt.content('Sine(θ): 1.00'); 65 | let cosineTxt = svg.create('text').set({ x: 510, y: 910 }) 66 | cosineTxt.content('Cosine(θ): 0.00'); 67 | 68 | svg.trackCursor(() => { 69 | 70 | let offset = Math.PI / 2; 71 | let angle = Gen.map(svg.cursorX, 0, 1000, 0, 360) 72 | let theta = angle * (Math.PI/180) + offset; 73 | let sin = Math.sin(theta); 74 | let cos = Math.cos(theta); 75 | let xPos = Gen.map(sin, -1, 1, 200, 800); 76 | let yPos = Gen.map(cos, -1, 1, 150, 750); 77 | 78 | hyp.set({ d: `M 500,450 L ${[xPos, yPos]}` }); 79 | opp.set({ d: `M ${[xPos, yPos]} L ${xPos} 450` }); 80 | adj.set({ d: `M 500,450 L ${xPos} 450` }); 81 | hypCircle.set({ cx: xPos, cy: yPos }); 82 | oppCircle.set({ cx: xPos, cy: 450 }); 83 | degreesTxt.content(`Degrees: ${Number(angle).toFixed()}`); 84 | radiansTxt.content(`Radians: ${Number(angle * (Math.PI/180)).toFixed(2)}`); 85 | sineTxt.content(`Sine(θ): ${Number(sin).toFixed(2)}`); 86 | cosineTxt.content(`Cosine(θ): ${Number(cos).toFixed(2)}`); 87 | 88 | }); 89 | 90 | document.addEventListener('keydown', (event) => { 91 | let key = event.key.toLowerCase(); 92 | if (key === 's') svg.save(); 93 | }); -------------------------------------------------------------------------------- /figures/3.8-gradients.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /figures/7.6-circular-loop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /figures/8.11-gradient-vs-flat-lighting.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 18 | 20 | 24 | 28 | 29 | 36 | 40 | 44 | 45 | 53 | 54 | 64 | 65 | 68 | 76 | 79 | 86 | 93 | 94 | 96 | 97 | 99 | 100 | 101 | David Matthew 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /figures/8.12-diffuse-vs-specular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 24 | 28 | 33 | 34 | 43 | 44 | 51 | 56 | 61 | 62 | 70 | 71 | 72 | 75 | 82 | 83 | 86 | 90 | 97 | 104 | 105 | 106 | 108 | 109 | 111 | 112 | 113 | David Matthew 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /sketches/18-making-things-move/sketch.js: -------------------------------------------------------------------------------- 1 | import { SvJs } from '../../node_modules/svjs/src/index.js'; 2 | 3 | // Parent SVG. 4 | const svg = new SvJs().addTo(document.getElementById('container')); 5 | 6 | // Viewport and viewBox (1:1 aspect ratio). 7 | const svgSize = Math.min(window.innerWidth, window.innerHeight); 8 | svg.set({ width: svgSize, height: svgSize, viewBox: '0 0 1000 1000' }); 9 | 10 | // Background. 11 | svg.create('rect').set({ 12 | x: 0, y: 0, width: 1000, height: 1000, fill: '#181818' 13 | }); 14 | 15 | // Arrays to contain our shapes and their colours. 16 | let palette = ['#34d399', '#6ee7b7', '#a7f3d0', '#d1fae5']; 17 | let shapes = []; 18 | 19 | // Initialise our four shapes. 20 | for (let i = 0; i < 4; i += 1) { 21 | let size = 500 - (i * 125); 22 | let position = 250 + (i * 62.5); 23 | let shape = svg.create('rect').set({ 24 | x: position, 25 | y: position, 26 | width: size, 27 | height: size, 28 | fill: palette[i], 29 | transform_origin: '50% 50%', 30 | transform: 'rotate(45)' 31 | }); 32 | shapes.push(shape); 33 | } 34 | 35 | //Set an id for our first shape. 36 | shapes[0].set({ id: 'cssShape' }); 37 | 38 | // Animate this shape with CSS. 39 | svg.create('style').content(` 40 | @keyframes scaleRotate { 41 | 0% { transform: rotate(0) scale(1, 1) } 42 | 50% { transform: rotate(180deg) scale(0.75, 1.5) } 43 | 100% { transform: rotate(360deg) scale(1, 1) } 44 | } 45 | 46 | #cssShape { 47 | animation-name: scaleRotate; 48 | animation-duration: 5s; 49 | animation-iteration-count: infinite; 50 | animation-timing-function: linear; 51 | } 52 | `); 53 | 54 | // Rotate the second shape using SMIL. 55 | shapes[1].create('animateTransform').set({ 56 | attributeName: 'transform', 57 | type: 'rotate', 58 | from: '0', 59 | to: '360', 60 | dur: '5s', 61 | repeatCount: 'indefinite' 62 | }); 63 | 64 | // Scale the shape using SMIL. 65 | shapes[1].create('animateTransform').set({ 66 | attributeName: 'transform', 67 | type: 'scale', 68 | values: '1 1; 0.75 1.5; 1 1', 69 | dur: '5s', 70 | additive: 'sum', 71 | repeatCount: 'indefinite' 72 | }); 73 | 74 | // Using the Web Animations API to scale and rotate our third shape. 75 | let keyframes = { 76 | transform: [ 77 | 'rotate(0deg) scale(1, 1)', 78 | 'rotate(180deg) scale(0.75, 1.5)', 79 | 'rotate(360deg) scale(1, 1)' 80 | ] 81 | }; 82 | 83 | let options = { 84 | duration: 5000, 85 | iterations: Infinity 86 | }; 87 | 88 | shapes[2].animate(keyframes, options); 89 | 90 | // Variables to set outside the animation loop. 91 | let isPositive = true; 92 | let scale = 0, tick = 0, prevTime = 0; 93 | 94 | // Animate the final shape using requestAnimationFrame. 95 | function animate(time) { 96 | 97 | // Prevent errors when the time is undefined on first frame. 98 | if (time === undefined) time = 0; 99 | 100 | // Rotate 360° in 5000ms: 360/5000 = 0.072. 101 | let angle = time * 0.072; 102 | 103 | // We need a constant tick value tied to the time that doesn't increment indefinitely. 104 | tick = time - prevTime; 105 | 106 | // Scale by 0.5 in 2500ms: 0.5/2500 = 0.0002. 107 | scale = isPositive ? scale + (tick * 0.0002) : scale - (tick * 0.0002); 108 | 109 | // Apply the rotation and scale values. 110 | shapes[3].set({ 111 | transform: `rotate(${angle}) scale(${1 - scale}, ${1 + scale})` 112 | }); 113 | 114 | // Flip the polarity if the scale value falls outside these bounds. 115 | if (scale < 0 || scale > 0.5) isPositive = !isPositive; 116 | 117 | // Capture the time before it increments. 118 | prevTime = time; 119 | 120 | // The recursive bit. 121 | requestAnimationFrame(animate); 122 | } 123 | 124 | animate(); 125 | 126 | -------------------------------------------------------------------------------- /figures/7.3-collision-detection.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /figures/4.14-masking.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 18 | 22 | 26 | 27 | 37 | 42 | 43 | 46 | 53 | 54 | 63 | 66 | 70 | 71 | 80 | 81 | 83 | 84 | 86 | image/svg+xml 87 | 89 | 90 | 91 | 92 | 99 | 106 | 115 | 118 | 122 | 126 | 127 | 131 | 134 | 138 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /figures/4.6-palettes.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 29 | 36 | 39 | 48 | 57 | 66 | 75 | 84 | 93 | 102 | 111 | 120 | 129 | 138 | 147 | 156 | 165 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /figures/6.8-large-arc-and-sweep.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 25 | 30 | 31 | 41 | 46 | 47 | 57 | 62 | 63 | 73 | 78 | 79 | 80 | 82 | 83 | 85 | image/svg+xml 86 | 88 | 89 | 90 | 91 | 98 | 103 | 108 | 113 | 118 | 122 | 126 | 131 | 136 | 144 | 152 | 153 | -------------------------------------------------------------------------------- /figures/4.15-porto-pareto.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /figures/8.6-coloured-grid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /figures/8.4-full-rgb-grid.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 12 | 20 | 25 | 29 | 35 | 40 | 41 | 42 | 49 | 52 | 59 | 66 | 74 | 82 | 89 | 96 | 103 | 110 | 117 | 124 | 131 | 138 | 145 | 152 | 159 | 167 | 175 | 182 | 189 | 196 | 203 | 210 | 217 | 224 | 231 | 238 | 245 | 253 | 260 | 268 | 275 | 282 | 289 | 296 | 297 | 298 | -------------------------------------------------------------------------------- /figures/6.6-quadratic-slinky.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /figures/1.3-first-generative-sketch.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /figures/7.5-radians-and-pi.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /figures/6.12-organic-curves.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------