-
-
Save zzzev/91d8387dda9c3729e42791427e49633e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// URL: https://observablehq.com/@zzzev/slit-scan-effect | |
// Title: Slit Scan Effect | |
// Author: Zev Youra (@zzzev) | |
// Version: 131 | |
// Runtime version: 1 | |
const m0 = { | |
id: "c56936685e3167a3@131", | |
variables: [ | |
{ | |
inputs: ["md"], | |
value: (function(md){return( | |
md`# Slit Scan Effect | |
An interpretation of the [slit-scan effect](https://en.wikipedia.org/wiki/Slit-scan_photography) as popularized in 2001: A Space Odyssey, and more recently [Netflix's recently updated load animation](https://gfycat.com/SlimImpishArkshell).` | |
)}) | |
}, | |
{ | |
name: "canvas", | |
inputs: ["DOM","width","height","d3","numRects","center"], | |
value: (function*(DOM,width,height,d3,numRects,center) | |
{ | |
const c = DOM.context2d(width, height) | |
function drawR(x = 0, w = 10, fill = "rgba(0,0,0,0.1)") { | |
fill = d3.color(fill); | |
const edge = d3.color(fill); | |
fill.opacity = 0.5; | |
edge.opacity = 0; | |
const g = c.createLinearGradient(x, 0, x + w, 0); | |
g.addColorStop(0, edge); | |
g.addColorStop(0.25, fill); | |
g.addColorStop(0.75, fill); | |
g.addColorStop(1, edge); | |
c.fillStyle = g; | |
c.fillRect(x, 0, w, height); | |
} | |
const xRand = d3.randomNormal(0, 10); | |
const wRand = d3.randomNormal(100, 100); | |
const fRand = d3.randomNormal(0, 1); | |
const vRand = d3.randomNormal(0, 1); | |
let rects = []; | |
while (true) { | |
c.clearRect(0, 0, width, height); | |
if (rects.length < numRects) { | |
const w = 1; | |
const tw = wRand(); | |
const x = center - (w / 2) + xRand(); | |
const f = d3.color(d3.interpolateRainbow(fRand())); | |
const v = vRand(); | |
rects.push({x, w, tw, f, v}) | |
} | |
rects.forEach((r) => { | |
const {x, w, tw, f, v} = r; | |
// const delta = center - (x + width / 2); | |
if (r.w < r.tw) { | |
r.w += 1; | |
r.x -= 0.5; | |
} | |
r.x = x + v; | |
r.v *= 1.01; | |
drawR(x, w, f); | |
}) | |
rects = rects.filter(({x, w}) => x < width && x + w > 0); | |
yield c.canvas; | |
} | |
} | |
) | |
}, | |
{ | |
name: "viewof numRects", | |
inputs: ["slider"], | |
value: (function(slider){return( | |
slider({min: 1, max: 400, step: 1, title: "Max Bars"}) | |
)}) | |
}, | |
{ | |
name: "numRects", | |
inputs: ["Generators","viewof numRects"], | |
value: (G, _) => G.input(_) | |
}, | |
{ | |
name: "center", | |
inputs: ["width"], | |
value: (function(width){return( | |
width / 2 | |
)}) | |
}, | |
{ | |
name: "height", | |
inputs: ["width"], | |
value: (function(width){return( | |
width * 9 / 16 | |
)}) | |
}, | |
{ | |
name: "d3", | |
inputs: ["require"], | |
value: (function(require){return( | |
require("d3@5") | |
)}) | |
}, | |
{ | |
from: "@jashkenas/inputs", | |
name: "slider", | |
remote: "slider" | |
} | |
] | |
}; | |
const m1 = { | |
id: "@jashkenas/inputs", | |
variables: [ | |
{ | |
name: "slider", | |
inputs: ["input"], | |
value: (function(input){return( | |
function slider(config = {}) { | |
let {value, min = 0, max = 1, step = "any", precision = 2, title, description, getValue, format, display, submit} = config; | |
if (typeof config == "number") value = config; | |
if (value == null) value = (max + min) / 2; | |
precision = Math.pow(10, precision); | |
if (!getValue) getValue = input => Math.round(input.valueAsNumber * precision) / precision; | |
return input({ | |
type: "range", title, description, submit, format, display, | |
attributes: {min, max, step, value}, | |
getValue | |
}); | |
} | |
)}) | |
}, | |
{ | |
name: "input", | |
inputs: ["html","d3format"], | |
value: (function(html,d3format){return( | |
function input(config) { | |
let { | |
form, | |
type = "text", | |
attributes = {}, | |
action, | |
getValue, | |
title, | |
description, | |
format, | |
display, | |
submit, | |
options | |
} = config; | |
if (!form) | |
form = html`<form> | |
<input name=input type=${type} /> | |
</form>`; | |
const input = form.input; | |
Object.keys(attributes).forEach(key => { | |
const val = attributes[key]; | |
if (val != null) input.setAttribute(key, val); | |
}); | |
if (submit) | |
form.append( | |
html`<input name=submit type=submit style="margin: 0 0.75em" value="${ | |
typeof submit == "string" ? submit : "Submit" | |
}" />` | |
); | |
form.append( | |
html`<output name=output style="font: 14px Menlo, Consolas, monospace; margin-left: 0.5em;"></output>` | |
); | |
if (title) | |
form.prepend( | |
html`<div style="font: 700 0.9rem sans-serif;">${title}</div>` | |
); | |
if (description) | |
form.append( | |
html`<div style="font-size: 0.85rem; font-style: italic;">${description}</div>` | |
); | |
if (format) format = d3format.format(format); | |
if (action) { | |
action(form); | |
} else { | |
const verb = submit | |
? "onsubmit" | |
: type == "button" | |
? "onclick" | |
: type == "checkbox" || type == "radio" | |
? "onchange" | |
: "oninput"; | |
form[verb] = e => { | |
e && e.preventDefault(); | |
const value = getValue ? getValue(input) : input.value; | |
if (form.output) | |
form.output.value = display | |
? display(value) | |
: format | |
? format(value) | |
: value; | |
form.value = value; | |
if (verb !== "oninput") | |
form.dispatchEvent(new CustomEvent("input", { bubbles: true })); | |
}; | |
if (verb !== "oninput") | |
input.oninput = e => e && e.stopPropagation() && e.preventDefault(); | |
if (verb !== "onsubmit") form.onsubmit = e => e && e.preventDefault(); | |
form[verb](); | |
} | |
return form; | |
} | |
)}) | |
}, | |
{ | |
name: "d3format", | |
inputs: ["require"], | |
value: (function(require){return( | |
require("d3-format@1") | |
)}) | |
} | |
] | |
}; | |
const notebook = { | |
id: "c56936685e3167a3@131", | |
modules: [m0,m1] | |
}; | |
export notebook; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment