Skip to content

Instantly share code, notes, and snippets.

@zerobias
Created June 8, 2019 03:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zerobias/65acce6f2b18ee8ca472ba4d369b0af8 to your computer and use it in GitHub Desktop.
Save zerobias/65acce6f2b18ee8ca472ba4d369b0af8 to your computer and use it in GitHub Desktop.
example reactive dom with effector https://dist-ccpqbn9uk.now.sh/
//@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])
}
}
}
//@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