Skip to content

Instantly share code, notes, and snippets.

@brodavi
Created April 11, 2016 12:35
Show Gist options
  • Save brodavi/f867b9a2afff5dd99c4c353e8e1fdf86 to your computer and use it in GitHub Desktop.
Save brodavi/f867b9a2afff5dd99c4c353e8e1fdf86 to your computer and use it in GitHub Desktop.
animated-letters as a sub-component
import Cycle from '@cycle/core'
import {Observable} from 'rx'
import {div, ul, li, makeDOMDriver} from 'cycle-snabbdom'
import {intersection, difference, sortBy} from 'lodash'
function intent(keydownSource) {
return keydownSource
.map(ev => ev.code.replace('Key', ''))
.filter(str => str.length === 1)
}
function model(action$) {
const initialState = ['A']
return action$.startWith(initialState).startWith([]).scan((acc, key) => {
const index = acc.indexOf(key)
if (index === -1) {
return acc.concat(key).sort()
}
const newAcc = acc.slice()
newAcc.splice(index, 1)
return newAcc
})
}
function determineDeltaPoints(state$) {
return state$.pairwise().flatMap(([before, after]) => {
const addedPoints = difference(after, before).map(key =>
({key, value: 0, target: 1})
)
const removedPoints = difference(before, after).map(key =>
({key, value: 1, target: 0})
)
const points = addedPoints.concat(removedPoints)
return Observable.from(sortBy(points, 'key'))
})
}
function expandAsRenderingFrames(point$) {
return point$.flatMapLatest(point =>
Observable.interval(10).map(point).take(100)
)
}
function calculateAnimationSteps(point$) {
function incorporateNewPoint(oldPoints, newPoint) {
const index = oldPoints.findIndex(point => point.key === newPoint.key)
let points
if (index === -1 && newPoint.target === 1) {
points = oldPoints.concat(newPoint)
} else {
points = oldPoints.slice()
points[index] = newPoint
}
return points
}
function progressEachPoint(oldPoints, newPoints) {
return newPoints.map(newPoint => {
const target = newPoint.target
const oldPoint = oldPoints.find(p => p.key === newPoint.key)
const value = !!oldPoint ? oldPoint.value : newPoint.value
return {
...newPoint,
value: (Math.abs(target - value) < 0.01) ?
target :
value + (target - value) * 0.05
}
})
}
return point$.scan((acc, point) => {
const newAcc = incorporateNewPoint(acc, point)
const progressedAcc = progressEachPoint(acc, newAcc)
const sanitizedAcc = progressedAcc.filter(point =>
!(point.target === 0 && point.value === 0)
)
const sortedAcc = sortBy(sanitizedAcc, 'key')
return sortedAcc
}, [])
}
function animate(state$) {
return state$
.let(determineDeltaPoints)
.let(expandAsRenderingFrames)
.let(calculateAnimationSteps)
}
function view(state$) {
const animatedState$ = animate(state$)
const ulStyle = {padding: '0', listStyle: 'none', display: 'flex'}
const liStyle = {fontSize: '50px'}
return animatedState$.map(animStates =>
ul({style: ulStyle}, animStates.map(animState =>
li({style: {fontSize: `${animState.value * 50}px`}}, animState.key)
))
)
}
function main(sources) {
const key$ = intent(sources.Keydown)
const state$ = model(key$)
const vtree$ = view(state$)
return {
DOM: vtree$,
}
}
export default main
import Rx from 'rx'
import Cycle from '@cycle/core'
import CycleDOM from '@cycle/dom'
const {div, makeDOMDriver} = CycleDOM
import {makeHTTPDriver} from '@cycle/http'
import isolate from '@cycle/isolate'
import AnimatedLetters from './animatedletters.js'
function main(sources) {
const aletters$ = AnimatedLetters(sources).DOM
const vtree$ = Rx.Observable.combineLatest(
aletters$,
(vd1) => {
return div([vd1])
}
)
return {
DOM: vtree$
};
}
const drivers = {
Keydown: () => Rx.Observable.fromEvent(document, 'keydown'),
DOM: CycleDOM.makeDOMDriver('#app')
}
Cycle.run(main, drivers)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment