Created
June 8, 2019 03:43
-
-
Save zerobias/65acce6f2b18ee8ca472ba4d369b0af8 to your computer and use it in GitHub Desktop.
example reactive dom with effector https://dist-ccpqbn9uk.now.sh/
This file contains hidden or 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
//@flow | |
/* eslint-disable no-unused-vars */ | |
import {createStore, createEvent, type Store, type Event} from 'effector' | |
declare export function element<P, R>(tag: (props: P) => R, props: P): R | |
declare export function element( | |
tag: 'span', | |
props: { | |
class?: {[field: string]: string}, | |
content: Store<string>, | |
}, | |
): HTMLInputElement | |
declare export function element( | |
tag: 'input', | |
props: { | |
max: Store<number>, | |
min: Store<number>, | |
oninput?: Event<InputEvent>, | |
step: Store<number>, | |
type: 'range', | |
value: Store<number>, | |
class?: {[field: string]: string}, | |
}, | |
): HTMLInputElement | |
declare export function element( | |
tag: 'canvas', | |
props: { | |
class?: {[field: string]: string}, | |
}, | |
): HTMLCanvasElement | |
declare export function element( | |
tag: 'div', | |
props: { | |
class?: {[field: string]: string}, | |
}, | |
...children: * | |
): HTMLCanvasElement | |
export function element(tag, props, ...children) { | |
if (typeof tag === 'function') return tag(props) | |
if (tag === 'input') { | |
const e = document.createElement(tag) | |
addClasses(e, props) | |
e.type = props.type | |
props.min.watch(val => { | |
e.min = val.toString() | |
}) | |
props.max.watch(val => { | |
e.max = val.toString() | |
}) | |
props.value.watch(val => { | |
e.value = val.toString() | |
}) | |
props.step.watch(val => { | |
e.step = val.toString() | |
}) | |
const oninput = props.oninput || createEvent() | |
e.addEventListener('input', oninput, true) | |
props.value.on(oninput, (val, e: any) => +e.currentTarget.value) | |
return e | |
} | |
if (tag === 'canvas') { | |
const e = document.createElement(tag) | |
addClasses(e, props) | |
return e | |
} | |
if (tag === 'div') { | |
const e = document.createElement(tag) | |
addClasses(e, props) | |
const fragment = document.createDocumentFragment() | |
children.forEach(child => fragment.appendChild(child)) | |
e.appendChild(fragment) | |
return e | |
} | |
if (tag === 'span') { | |
const e = document.createElement(tag) | |
addClasses(e, props) | |
let textNode = document.createTextNode('') | |
e.appendChild(textNode) | |
props.content | |
.map(content => document.createTextNode(content)) | |
.watch(text => { | |
e.replaceChild(text, textNode) | |
textNode = text | |
}) | |
return e | |
} | |
throw Error(`wrong tag "${String(tag)}"`) | |
} | |
function addClasses(node: any, props: any) { | |
if ('class' in props) { | |
for (const key in props.class) { | |
node.classList.add(props.class[key]) | |
} | |
} | |
} |
This file contains hidden or 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
//@flow | |
/* eslint-disable no-unused-vars */ | |
//@jsx element | |
import {css} from 'linaria' | |
//$off | |
import {scaleSqrt, scaleLog} from 'd3-scale' | |
import { | |
createStore, | |
createStoreObject, | |
createEvent, | |
type Store, | |
type Event, | |
} from 'effector' | |
import {element} from './element' | |
const body = document.body | |
if (!body) throw Error('nobody') | |
const c = ( | |
<canvas | |
class={{ | |
_: css` | |
width: 800px; | |
height: 250px; | |
position: absolute; | |
top: 20px; | |
left: 340px; | |
:global() { | |
body { | |
margin: 0; | |
overflow: hidden; | |
background: #333; | |
} | |
} | |
`, | |
}} | |
/> | |
) | |
body.appendChild(c) | |
c.width = c.height * (c.clientWidth / c.clientHeight) | |
const numValueToText = n => (n === (n | 0) ? n.toString() : n.toFixed(1)) | |
const sliderBlock = css` | |
display: flex; | |
flex-direction: column; | |
width: 50px; | |
margin: 1rem; | |
align-items: center; | |
` | |
const inputSlider = css` | |
-webkit-appearance: slider-vertical; | |
width: 30px; | |
height: 120px; | |
` | |
const slidersBlock = css` | |
display: flex; | |
flex-direction: row; | |
width: 150px; | |
margin: 0; | |
` | |
const Slider = ({value, min, max, label}) => ( | |
<div class={{sliderBlock}}> | |
<span content={value.map(numValueToText)} /> | |
<input | |
type="range" | |
min={min} | |
max={max} | |
value={value} | |
step={createStore(0.1)} | |
class={{inputSlider}} | |
/> | |
<span content={createStore(label)} /> | |
</div> | |
) | |
const Sliders = ({min, max, value, header}) => ( | |
<div | |
class={{ | |
_: css` | |
display: flex; | |
flex-direction: column; | |
width: 150px; | |
align-items: center; | |
color: white; | |
`, | |
}}> | |
<span content={header} /> | |
<div class={{slidersBlock}}> | |
<Slider | |
label="value" | |
value={value.value} | |
min={value.min} | |
max={value.max} | |
/> | |
<Slider label="max" value={max.value} min={max.min} max={max.max} /> | |
<Slider label="min" value={min.value} min={min.min} max={min.max} /> | |
</div> | |
</div> | |
) | |
const frequencyMax = createStore(7.7) | |
const exponent = createStore(5.2) | |
const domainMax = createStore(7.3) | |
const freqMX = numberRange(frequencyMax, {min: 4, max: 10}) | |
const domainMX = numberRange(domainMax, {min: 4, max: 10}) | |
const expMX = numberRange(exponent, {min: 0.1, max: 10}) | |
body.appendChild( | |
<div | |
class={{ | |
_: css` | |
display: flex; | |
flex-direction: column; | |
width: 150px; | |
margin: 1rem; | |
align-items: center; | |
`, | |
}}> | |
<Sliders | |
header={createStore('frequency max')} | |
value={freqMX} | |
min={numberRange(freqMX.min, { | |
min: 2, | |
max: 5, | |
})} | |
max={numberRange(freqMX.max, { | |
min: 7, | |
max: 15, | |
})} | |
/> | |
<Sliders | |
header={createStore('exponent')} | |
value={expMX} | |
min={numberRange(expMX.min, { | |
min: 0, | |
max: 3, | |
})} | |
max={numberRange(expMX.max, { | |
min: 7, | |
max: 15, | |
})} | |
/> | |
<Sliders | |
header={createStore('domain max')} | |
value={domainMX} | |
min={numberRange(domainMX.min, { | |
min: 2, | |
max: 5, | |
})} | |
max={numberRange(domainMX.max, { | |
min: 7, | |
max: 15, | |
})} | |
/> | |
</div>, | |
) | |
const ctx = c.getContext('2d') | |
const cw = (c.width = 400) | |
const ch = (c.height = 250) | |
const cx = cw / 2 | |
const cy = ch / 2 | |
const rad = Math.PI / 180 | |
const w = 400 | |
const h = 200 | |
const amplitude = h | |
let frequency = 0.015 | |
const freqStep = 0.001 | |
const freqMin = 0.01 | |
const freqMax = 0.08 | |
const domMin = 0.005 | |
const domMax = 0.08 | |
let up = true | |
const phiQ = 1 | |
let phi = 0 | |
let frames = 0 | |
let y | |
//ctx.strokeStyle = "Cornsilk"; | |
ctx.lineWidth = 5 | |
const freqScale = createStoreObject({ | |
frequencyMax: frequencyMax.map(n => n / 100), | |
exponent, | |
domainMax: domainMax.map(n => n / 100), | |
}).map(({frequencyMax, exponent, domainMax}) => ({ | |
freq: scaleSqrt() | |
.exponent(exponent) | |
.domain([domMin, domainMax]) | |
.range([freqMin, frequencyMax]), | |
color: scaleSqrt() | |
.exponent(exponent) | |
.domain([domMin, domainMax]) | |
.range(['blue', 'orange', 'red']), | |
})) | |
let currentFreq | |
function Draw() { | |
frames++ | |
phi = frames % phiQ | |
if (up) { | |
frequency += freqStep | |
} else { | |
frequency -= freqStep | |
} | |
if (frequency > freqMax) { | |
up = false | |
} else if (frequency < freqMin) { | |
up = true | |
} | |
currentFreq = freqScale.getState().freq(frequency) | |
ctx.clearRect(0, 0, cw, ch) | |
ctx.beginPath() | |
ctx.strokeStyle = freqScale.getState().color(frequency) // `hsl(${(frequency * 4000) | 0},100%,50%)` | |
ctx.moveTo(0, ch) | |
for (let x = 0; x < w; x++) { | |
y = ((2 + Math.cos(x * currentFreq + phi)) * amplitude) / 4 | |
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2; | |
ctx.lineTo(x, y + 40) // 40 = offset | |
} | |
ctx.lineTo(w, ch) | |
ctx.lineTo(0, ch) | |
ctx.stroke() | |
requestAnimationFrame(Draw) | |
} | |
requestAnimationFrame(Draw) | |
function minMaxRaw({min, max, value}) { | |
value | |
.on(max, (val, max) => Math.min(val, max)) | |
.on(min, (val, min) => Math.max(val, min)) | |
min.on(max, (min, max) => Math.min(min, max)) | |
max.on(min, (max, min) => Math.max(min, max)) | |
return {min, max, value} | |
} | |
function numberRange(value, {min, max}) { | |
return minMaxRaw({ | |
value, | |
min: createStore(min), | |
max: createStore(max), | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment