-
-
Save zzzev/49b0d12164fb2d5d98bd30f8ca53cf67 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 default notebook; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment