Generating Art Everywhere and Fast!

A presentation at NEJS Conf in August 2019 in Omaha, NE, USA by Trent Willis

Slide 1

Slide 1

GENERATE ART EVERYWHERE ) s t n e n o p m o c b e (with w AND(wFAST! ith web workers) @trentmwillis | #NEJSConf

Slide 2

Slide 2

Have you ever been a part of an online forum community?

Slide 3

Slide 3

Slide 4

Slide 4

Slide 5

Slide 5

Slide 6

Slide 6

Slide 7

Slide 7

Slide 8

Slide 8

JavaScript + Art

Slide 9

Slide 9

JavaScript + Art = Fun

Slide 10

Slide 10

Too often art stands alone.

Slide 11

Slide 11

Too often art stands alone. What if we embedded it in existing user experiences?

Slide 12

Slide 12

! n e p p a h s i h t e k a m o t g n i o g e r We’ <generate-art></generate-art>

Slide 13

Slide 13

GENERATE ART EVERYWHERE ) s t n e n o p m o c b e (with w AND(wFAST! ith web workers) @trentmwillis | #NEJSConf

Slide 14

Slide 14

What type of art should we create with JavaScript? @trentmwillis | #NEJSConf

Slide 15

Slide 15

What type of art should we create with JavaScript? Generative Art @trentmwillis | #NEJSConf

Slide 16

Slide 16

Generative Art is created by “non-human systems” Input Generated Art System @trentmwillis | #NEJSConf

Slide 17

Slide 17

Generative Computer == Art Art @trentmwillis | #NEJSConf

Slide 18

Slide 18

Generative Computer !== Art Art @trentmwillis | #NEJSConf

Slide 19

Slide 19

Not strictly the same thing. But loosely the same. Generative Computer !== Art Art @trentmwillis | #NEJSConf

Slide 20

Slide 20

Generative Art Algorithmic Robotic Biological Computer Art Art Chemical Art Art Art @trentmwillis | #NEJSConf

Slide 21

Slide 21

Algorithmic Computer Art @trentmwillis | #NEJSConf

Slide 22

Slide 22

@trentmwillis | #NEJSConf

Slide 23

Slide 23

JavaScript is good for Algorithmic Computer Art @trentmwillis | #NEJSConf

Slide 24

Slide 24

There are two ways to approach art in JavaScript Manual Approach Library Approach <canvas />, Web APIs p5.js, d3.js, chromata @trentmwillis | #NEJSConf

Slide 25

Slide 25

Use both approac hes! <canvas /> + p5.js @trentmwillis | #NEJSConf

Slide 26

Slide 26

@trentmwillis | #NEJSConf

Slide 27

Slide 27

glitch.com/~nejs-demo-1 @trentmwillis | #NEJSConf

Slide 28

Slide 28

<canvas id=”canvas”></canvas> @trentmwillis | #NEJSConf

Slide 29

Slide 29

<canvas id=”canvas”></canvas> <script src=”./generate-art.js”></script> @trentmwillis | #NEJSConf

Slide 30

Slide 30

// generate-art.js const canvas = document.getElementById(‘canvas’); setup(canvas); setup render(canvas); @trentmwillis | #NEJSConf

Slide 31

Slide 31

const setup = (canvas) => { }; @trentmwillis | #NEJSConf

Slide 32

Slide 32

const setup = (canvas) => { canvas.width = 500; canvas.height = 280; }; @trentmwillis | #NEJSConf

Slide 33

Slide 33

// generate-art.js const canvas = document.getElementById(‘canvas’); setup(canvas); render(canvas); @trentmwillis | #NEJSConf

Slide 34

Slide 34

const render = (canvas) => { }; @trentmwillis | #NEJSConf

Slide 35

Slide 35

const render = (canvas) => { const drawingContext = canvas.getContext(‘2d’); }; @trentmwillis | #NEJSConf

Slide 36

Slide 36

const render = (canvas) => { const drawingContext = canvas.getContext(‘2d’); const seedValue = 9; const randomNumberGenerator = pseudoRandomNumberGenerator(seedValue); };; @trentmwillis | #NEJSConf

Slide 37

Slide 37

const render = (canvas) => { const drawingContext = canvas.getContext(‘2d’); const seedValue = 9; const randomNumberGenerator = pseudoRandomNumberGenerator(seedValue); const drawNextPoint = () => { // Draw a point using the randomNumberGenerator on the drawingContext }; };; @trentmwillis | #NEJSConf

Slide 38

Slide 38

const render = (canvas) => { const drawingContext = canvas.getContext(‘2d’); const seedValue = 9; const randomNumberGenerator = pseudoRandomNumberGenerator(seedValue); const drawNextPoint = () => { // Draw a point using the randomNumberGenerator on the drawingContext }; drawNextPoint(); }; @trentmwillis | #NEJSConf

Slide 39

Slide 39

const render = (canvas) => { const drawingContext = canvas.getContext(‘2d’); const seedValue = 9; const randomNumberGenerator = pseudoRandomNumberGenerator(seedValue); const drawNextPoint = () => { // Draw a point using the randomNumberGenerator on the drawingContext requestAnimationFrame(drawNextPoint); }; drawNextPoint(); }; @trentmwillis | #NEJSConf

Slide 40

Slide 40

How do we generate something that looks nice? @trentmwillis | #NEJSConf

Slide 41

Slide 41

How do we generate something that looks nice? Artistic Principles @trentmwillis | #NEJSConf

Slide 42

Slide 42

Artistic Principles help us create pleasing art Balance Contrast Emphasis Movement Proportion Repetition Variety @trentmwillis | #NEJSConf

Slide 43

Slide 43

M O V E M E N T @trentmwillis | #NEJSConf

Slide 44

Slide 44

repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition repetition @trentmwillis | #NEJSConf

Slide 45

Slide 45

Variety Variety Variety Variety Variety @trentmwillis | #NEJSConf

Slide 46

Slide 46

Artistic principles help us create pleasing art Balance Contrast Emphasis Movement Proportion Repetition Variety @trentmwillis | #NEJSConf

Slide 47

Slide 47

Artistic principles help us create pleasing art Balance Contrast Emphasis Movement Proportion Repetition Variety Response @trentmwillis | #NEJSConf

Slide 48

Slide 48

Responsive Art @trentmwillis | #NEJSConf

Slide 49

Slide 49

@trentmwillis | #NEJSConf

Slide 50

Slide 50

glitch.com/~nejs-demo-2 @trentmwillis | #NEJSConf

Slide 51

Slide 51

p5 + p5.sound ———————————— github.com/processing/p5.js @trentmwillis | #NEJSConf

Slide 52

Slide 52

const microphone = new p5.AudioIn(); @trentmwillis | #NEJSConf

Slide 53

Slide 53

const microphone = new p5.AudioIn(); microphone.start(() => { }); @trentmwillis | #NEJSConf

Slide 54

Slide 54

const microphone = new p5.AudioIn(); microphone.start(() => { const seed = microphone.getLevel(); }); @trentmwillis | #NEJSConf

Slide 55

Slide 55

const microphone = new p5.AudioIn(); microphone.start(() => { const seed = microphone.getLevel(); render(canvas, seed); }); @trentmwillis | #NEJSConf

Slide 56

Slide 56

n a h t r e i s a e h c u m So . I P A o i d u A b e W with the const microphone = new p5.AudioIn(); microphone.start(() => { const seed = microphone.getLevel(); render(canvas, seed); }); @trentmwillis | #NEJSConf

Slide 57

Slide 57

@trentmwillis | #NEJSConf

Slide 58

Slide 58

@trentmwillis | #NEJSConf

Slide 59

Slide 59

<style> .container { position: relative; display: flex; margin: 0; align-items: center; justify-content: center; font-family: ‘Nunito’, sans-serif; background: rgba(245, 247, 250, 1); } #start { position: absolute; color: rgba(245, 247, 250, 1); font-size: 1.5rem; background: rgba(135, 25, 224, 1); border-radius: 4px; border: none; padding: 0.5rem 1rem; cursor: pointer; transition: opacity 1s, background 0.3s, transform 0.1s; } #start:hover { background: rgba(207, 17, 36, 1); } #start:active { transform: scale(0.95); } #canvas { background: black; transition: opacity 5s; opacity: 1; } #notification { position: absolute; color: rgba(135, 25, 224, 1); font-size: 2rem; animation: pulse 2s infinite; transition: opacity 5s; max-width: 500px; text-align: center; } .is-hidden { opacity: 0 !important; pointer-events: none; } @keyframes pulse 0% { text-shadow: } 90% { text-shadow: } 100% { text-shadow: } } </style>

{ f o t o l a s ’ t ! a e h g a T m i e n o r o f e d co 0 0 0 rgba(135, 25, 224, 1), 0 0 0 rgba(135, 25, 224, 1); 0 0 2em rgba(135, 25, 224, 0), 0 0 1em rgba(135, 25, 224, 0); 0 0 0 rgba(135, 25, 224, 0), 0 0 0 rgba(135, 25, 224, 0); <canvas id=”canvas” class=”is-hidden”></canvas> <div id=”notification” class=”is-hidden”>Sampling noise for art generation</div> <button id=”start”>Generate Art</button> @trentmwillis | #NEJSConf

Slide 60

Slide 60

. e d o c f o t n u o m a e l b a n o s a e r a s ’ t a h T <generate-art width=”750” height=”250”></generate-art> @trentmwillis | #NEJSConf

Slide 61

Slide 61

Web Components let us encapsulate all that markup and presentation <generate-art width=”750” height=”250”></generate-art> @trentmwillis | #NEJSConf

Slide 62

Slide 62

Web Components make it easy to reuse your art without much extra code <generate-art width=”750” height=”250”></generate-art> <script src=”./generate-art-component.js”></script> @trentmwillis | #NEJSConf

Slide 63

Slide 63

@trentmwillis | #NEJSConf

Slide 64

Slide 64

@trentmwillis | #NEJSConf

Slide 65

Slide 65

class GenerateArt extends HTMLElement { }~ @trentmwillis | #NEJSConf

Slide 66

Slide 66

class GenerateArt extends HTMLElement { constructor() { super(); }; }~ @trentmwillis | #NEJSConf

Slide 67

Slide 67

class GenerateArt extends HTMLElement { constructor() { super(); const shadowDOM = this.attachShadow({mode: ‘closed’}); }; }~ @trentmwillis | #NEJSConf

Slide 68

Slide 68

class GenerateArt extends HTMLElement { constructor() { super(); const shadowDOM = this.attachShadow({mode: ‘closed’}); shadowDOM.innerHTML = markupForGenerateArtComponent; }; }~ @trentmwillis | #NEJSConf

Slide 69

Slide 69

class GenerateArt extends HTMLElement { constructor() { super(); const shadowDOM = this.attachShadow({mode: ‘closed’}); shadowDOM.innerHTML = markupForGenerateArtComponent; this.setDimensions( shadowDOM, this.getAttribute(‘width’), this.getAttribute(‘height’), ); }; }~ @trentmwillis | #NEJSConf

Slide 70

Slide 70

class GenerateArt extends HTMLElement { constructor() { super(); const shadowDOM = this.attachShadow({mode: ‘closed’}); shadowDOM.innerHTML = markupForGenerateArtComponent; this.setDimensions( shadowDOM, this.getAttribute(‘width’), this.getAttribute(‘height’), ); this.setupGenerateArtButton(shadowDOM); }; }~ @trentmwillis | #NEJSConf

Slide 71

Slide 71

class GenerateArt extends HTMLElement { constructor() { super(); const shadowDOM = this.attachShadow({mode: ‘closed’}); shadowDOM.innerHTML = markupForGenerateArtComponent; this.setDimensions( shadowDOM, this.getAttribute(‘width’), this.getAttribute(‘height’), ); this.setupGenerateArtButton(shadowDOM); }; }~ customElements.define(‘generate-art’, GenerateArt); @trentmwillis | #NEJSConf

Slide 72

Slide 72

@trentmwillis | #NEJSConf

Slide 73

Slide 73

glitch.com/~nejs-demo-3 @trentmwillis | #NEJSConf

Slide 74

Slide 74

@trentmwillis | #NEJSConf

Slide 75

Slide 75

Primum non nocere @trentmwillis | #NEJSConf

Slide 76

Slide 76

Primum non nocere ———————————— First, do no harm. @trentmwillis | #NEJSConf

Slide 77

Slide 77

Jank

Harm @trentmwillis | #NEJSConf

Slide 78

Slide 78

Jank happens when your code prevents a user’s action from being processed Click Render Action @trentmwillis | #NEJSConf

Slide 79

Slide 79

Use Web Workers to reduce jank Without Web Workers Main Click Render Action Worker With Web Workers Main Click Action Render @trentmwillis | #NEJSConf

Slide 80

Slide 80

Use OffscreenCanvas with Web Workers to reduce jank in your art Regular Canvas Main Thread @trentmwillis | #NEJSConf

Slide 81

Slide 81

Use OffscreenCanvas with Web Workers to reduce jank in your art Regular Canvas .transferControlToOffscreen() Offscreen Canvas Main Thread @trentmwillis | #NEJSConf

Slide 82

Slide 82

Use OffscreenCanvas with Web Workers to reduce jank in your art Regular Canvas .transferControlToOffscreen() Offscreen Canvas .postMessage() Main Thread Web Worker @trentmwillis | #NEJSConf

Slide 83

Slide 83

const canvas = shadowDOM.getElementById(‘canvas’); @trentmwillis | #NEJSConf

Slide 84

Slide 84

const canvas = shadowDOM.getElementById(‘canvas’); const offscreenCanvas = canvas.transferControlToOffscreen(); @trentmwillis | #NEJSConf

Slide 85

Slide 85

const canvas = shadowDOM.getElementById(‘canvas’); const offscreenCanvas = canvas.transferControlToOffscreen(); const worker = new Worker(‘render-worker.js’); @trentmwillis | #NEJSConf

Slide 86

Slide 86

const canvas = shadowDOM.getElementById(‘canvas’); const offscreenCanvas = canvas.transferControlToOffscreen(); const worker = new Worker(‘render-worker.js’); worker.postMessage({ canvas: offscreenCanvas, seed: sampledValue }, [offscreenCanvas]); @trentmwillis | #NEJSConf

Slide 87

Slide 87

// render-worker.js onmessage = (event) => { }; @trentmwillis | #NEJSConf

Slide 88

Slide 88

// render-worker.js onmessage = (event) => { render(event.data.canvas, event.data.seed); }; @trentmwillis | #NEJSConf

Slide 89

Slide 89

@trentmwillis | #NEJSConf

Slide 90

Slide 90

glitch.com/~nejs-demo-4 @trentmwillis | #NEJSConf

Slide 91

Slide 91

! p u t e s g n i r o b e h t p i k S ez-offscreen-canvas ———————————— github.com/trentmwillis/ez-offscreen-canvas @trentmwillis | #NEJSConf

Slide 92

Slide 92

const canvas = document.querySelector(‘canvas’); @trentmwillis | #NEJSConf

Slide 93

Slide 93

const canvas = document.querySelector(‘canvas’); const seed = getRandomNumber(); @trentmwillis | #NEJSConf

Slide 94

Slide 94

const canvas = document.querySelector(‘canvas’); const seed = getRandomNumber(); const worker = ezOffscreenCanvas( canvas, { seed }, ({ canvas, seed }) => { // Rendering logic to run in Web Worker // using OffscreenCanvas }; );; @trentmwillis | #NEJSConf

Slide 95

Slide 95

const canvas = document.querySelector(‘canvas’); const seed = getRandomNumber(); const worker = ezOffscreenCanvas( canvas, { seed }, ({ canvas, seed }) => { // Rendering logic to run in Web Worker // using OffscreenCanvas }; );; // …some time later… worker.terminate(); @trentmwillis | #NEJSConf

Slide 96

Slide 96

We started here… Generative Art Web Component Web Worker OffscreenCanvas d e d n e d n a … here! @trentmwillis | #NEJSConf

Slide 97

Slide 97

The Web is a great and practical creative outlet @trentmwillis | #NEJSConf

Slide 98

Slide 98

! s i h t x i m re glitch.com/~offscreen-canvas-kit @trentmwillis | #NEJSConf