mirror of
https://github.com/Retropex/bitfeed.git
synced 2025-05-12 19:20:46 +02:00
Smoother, fancier animations
This commit is contained in:
parent
d3c84c7406
commit
29627a5ebd
@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import analytics from '../utils/analytics.js'
|
||||
import { fly } from 'svelte/transition'
|
||||
import { linear } from 'svelte/easing'
|
||||
import { smootherstep } from '../utils/easing.js'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from '../components/Icon.svelte'
|
||||
import closeIcon from '../assets/icon/cil-x-circle.svg'
|
||||
@ -80,16 +80,16 @@
|
||||
$: {
|
||||
if (!$blockTransitionDirection || !visible || !block || !$blocksEnabled) {
|
||||
transitionDirection = 'up'
|
||||
flyIn = { y: (restoring ? -50 : 50), duration: (restoring ? 500 : 1000), easing: linear, delay: (restoring ? 0 : newBlockDelay) }
|
||||
flyOut = { y: -50, duration: 2000, easing: linear }
|
||||
flyIn = { y: (restoring ? -50 : 50), duration: (restoring ? 1500 : 1000), easing: smootherstep, delay: (restoring ? 0 : newBlockDelay) }
|
||||
flyOut = { y: -50, duration: 1500, easing: smootherstep, delay: 0 }
|
||||
} else if ($blockTransitionDirection && $blockTransitionDirection === 'right') {
|
||||
transitionDirection = 'right'
|
||||
flyIn = { x: 100, easing: linear, delay: 1000, duration: 1000 }
|
||||
flyOut = { x: -100, easing: linear, delay: 0, duration: 1000 }
|
||||
flyIn = { x: 100, easing: smootherstep, delay: 1000, duration: 1000 }
|
||||
flyOut = { x: -100, easing: smootherstep, delay: 0, duration: 1000 }
|
||||
} else if ($blockTransitionDirection && $blockTransitionDirection === 'left') {
|
||||
transitionDirection = 'left'
|
||||
flyIn = { x: -100, easing: linear, delay: 1000, duration: 1000 }
|
||||
flyOut = { x: 100, easing: linear, delay: 0, duration: 1000 }
|
||||
flyIn = { x: -100, easing: smootherstep, delay: 1000, duration: 1000 }
|
||||
flyOut = { x: 100, easing: smootherstep, delay: 0, duration: 1000 }
|
||||
} else {
|
||||
transitionDirection = 'down'
|
||||
}
|
||||
|
@ -207,6 +207,7 @@ export default class TxController {
|
||||
|
||||
if (!this.explorerBlockScene) this.clearBlock()
|
||||
|
||||
this.poolScene.scrollLock = true
|
||||
if (this.blocksEnabled) {
|
||||
this.blockScene = new TxBlockScene({ width: this.blockAreaSize, height: this.blockAreaSize, blockId: block.id, controller: this, colorMode: this.colorMode })
|
||||
let knownCount = 0
|
||||
@ -239,7 +240,6 @@ export default class TxController {
|
||||
currentBlock.set(block)
|
||||
}
|
||||
} else {
|
||||
this.poolScene.scrollLock = true
|
||||
for (let i = 0; i < block.txns.length; i++) {
|
||||
if (this.txs[block.txns[i].id] && this.txs[block.txns[i].id].view) {
|
||||
this.txs[block.txns[i].id].view.update({
|
||||
@ -304,7 +304,7 @@ export default class TxController {
|
||||
enterFromRight = true
|
||||
}
|
||||
else prevBlockScene.exitRight()
|
||||
prevBlockScene.expire(2000)
|
||||
prevBlockScene.expire(3000)
|
||||
} else if (this.blockScene) {
|
||||
this.blockScene.exitRight()
|
||||
}
|
||||
@ -343,7 +343,7 @@ export default class TxController {
|
||||
const prevBlock = this.explorerBlock
|
||||
const prevBlockScene = this.explorerBlockScene
|
||||
prevBlockScene.exitLeft()
|
||||
prevBlockScene.expire(2000)
|
||||
prevBlockScene.expire(3000)
|
||||
this.explorerBlockScene = null
|
||||
this.explorerBlock = null
|
||||
urlPath.set("/")
|
||||
@ -384,6 +384,10 @@ export default class TxController {
|
||||
scene.remove(id)
|
||||
})
|
||||
if (this.txs[id]) this.txs[id].destroy()
|
||||
this.deleteTx(id)
|
||||
}
|
||||
|
||||
deleteTx (id) {
|
||||
delete this.txs[id]
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@ export default class BitcoinTx {
|
||||
this.is_partial = !!partial
|
||||
this.is_preview = !!preview
|
||||
this.id = id
|
||||
this.pixelPosition = { x: 0, y: 0, r: 0}
|
||||
if (!this.pixelPosition) this.pixelPosition = { x: 0, y: 0, r: 0}
|
||||
this.screenPosition = { x: 0, y: 0, r: 0}
|
||||
this.gridPosition = { x: 0, y: 0, r: 0}
|
||||
this.inputs = inputs
|
||||
|
@ -1,6 +1,7 @@
|
||||
import TxMondrianPoolScene from './TxMondrianPoolScene.js'
|
||||
import { settings } from '../stores.js'
|
||||
import { logTxSize, byteTxSize } from '../utils/misc.js'
|
||||
import { orange } from '../utils/color.js'
|
||||
import config from '../config.js'
|
||||
|
||||
let settingsValue
|
||||
@ -62,6 +63,7 @@ export default class TxBlockScene extends TxMondrianPoolScene {
|
||||
}
|
||||
|
||||
setTxOnScreen (tx, pixelPosition) {
|
||||
this.saveGridToPixelPosition(tx)
|
||||
if (!tx.view.initialised) {
|
||||
tx.view.update({
|
||||
display: {
|
||||
@ -95,14 +97,17 @@ export default class TxBlockScene extends TxMondrianPoolScene {
|
||||
color: tx.getColor('block', this.colorMode).color
|
||||
},
|
||||
duration: this.laidOut ? 1000 : 2000,
|
||||
delay: 0,
|
||||
jitter: this.laidOut ? 0 : 1500,
|
||||
delay: 50,
|
||||
jitter: this.laidOut ? 500 : 1500,
|
||||
smooth: true,
|
||||
state: 'block'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
prepareTxOnScreen (tx) {
|
||||
prepareTxOnScreen (tx, now) {
|
||||
const oldRadius = tx.pixelPosition.r
|
||||
this.saveGridToPixelPosition(tx)
|
||||
if (!tx.view.initialised) {
|
||||
tx.view.update({
|
||||
display: {
|
||||
@ -119,21 +124,40 @@ export default class TxBlockScene extends TxMondrianPoolScene {
|
||||
delay: 0,
|
||||
state: 'ready'
|
||||
})
|
||||
} else {
|
||||
const jitter = (Math.random() * 1700)
|
||||
tx.view.update({
|
||||
display: {
|
||||
position: {
|
||||
r: oldRadius + Math.max(2, oldRadius * 0.2)
|
||||
},
|
||||
},
|
||||
delay: 50 + jitter,
|
||||
start: now,
|
||||
duration: 750,
|
||||
smooth: true,
|
||||
boomerang: true
|
||||
})
|
||||
tx.view.update({
|
||||
display: {
|
||||
color: settingsValue.darkMode && false ? {
|
||||
h: 0.8,
|
||||
l: 1.0
|
||||
} : orange,
|
||||
},
|
||||
start: now,
|
||||
delay: 50 + jitter,
|
||||
duration: 500,
|
||||
})
|
||||
}
|
||||
tx.view.update({
|
||||
display: {
|
||||
color: tx.getColor('block', this.colorMode).color
|
||||
},
|
||||
duration: 2000,
|
||||
delay: 0
|
||||
})
|
||||
}
|
||||
|
||||
prepareTx (tx, sequence) {
|
||||
this.prepareTxOnScreen(tx, this.layoutTx(tx, sequence, 0, false))
|
||||
this.place(tx)
|
||||
this.prepareTxOnScreen(tx)
|
||||
}
|
||||
|
||||
enterTx (tx, right) {
|
||||
enterTx (tx, start, right) {
|
||||
tx.view.update({
|
||||
display: {
|
||||
position: {
|
||||
@ -157,8 +181,11 @@ export default class TxBlockScene extends TxMondrianPoolScene {
|
||||
alpha: 1
|
||||
}
|
||||
},
|
||||
start,
|
||||
duration: 2000,
|
||||
delay: 0
|
||||
delay: 100,
|
||||
jitter: 500,
|
||||
smooth: true,
|
||||
})
|
||||
}
|
||||
|
||||
@ -166,8 +193,9 @@ export default class TxBlockScene extends TxMondrianPoolScene {
|
||||
this.hidden = false
|
||||
this.exited = false
|
||||
const ids = this.getActiveTxList()
|
||||
const start = performance.now()
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
this.enterTx(this.txs[ids[i]], right)
|
||||
this.enterTx(this.txs[ids[i]], start, right)
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,7 +207,7 @@ export default class TxBlockScene extends TxMondrianPoolScene {
|
||||
this.enter(false)
|
||||
}
|
||||
|
||||
exitTx (tx, right) {
|
||||
exitTx (tx, start, right) {
|
||||
tx.view.update({
|
||||
display: {
|
||||
position: {
|
||||
@ -192,8 +220,11 @@ export default class TxBlockScene extends TxMondrianPoolScene {
|
||||
alpha: 0
|
||||
}
|
||||
},
|
||||
delay: 0,
|
||||
duration: 2000
|
||||
delay: 100,
|
||||
start,
|
||||
jitter: 500,
|
||||
duration: 2000,
|
||||
smooth: true,
|
||||
})
|
||||
}
|
||||
|
||||
@ -201,8 +232,9 @@ export default class TxBlockScene extends TxMondrianPoolScene {
|
||||
this.hidden = true
|
||||
this.exited = true
|
||||
const ids = this.getActiveTxList()
|
||||
const start = performance.now()
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
this.exitTx(this.txs[ids[i]], right)
|
||||
this.exitTx(this.txs[ids[i]], start, right)
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,7 +246,7 @@ export default class TxBlockScene extends TxMondrianPoolScene {
|
||||
this.exit(false)
|
||||
}
|
||||
|
||||
hideTx (tx) {
|
||||
hideTx (tx, now) {
|
||||
this.savePixelsToScreenPosition(tx)
|
||||
tx.view.update({
|
||||
display: {
|
||||
@ -225,13 +257,15 @@ export default class TxBlockScene extends TxMondrianPoolScene {
|
||||
alpha: 0
|
||||
}
|
||||
},
|
||||
duration: 2000,
|
||||
delay: 0,
|
||||
state: 'fadeout'
|
||||
start: now,
|
||||
duration: 1500,
|
||||
delay: 50,
|
||||
state: 'fadeout',
|
||||
smooth: true,
|
||||
})
|
||||
}
|
||||
|
||||
showTx (tx) {
|
||||
showTx (tx, now) {
|
||||
this.savePixelsToScreenPosition(tx)
|
||||
tx.view.update({
|
||||
display: {
|
||||
@ -242,13 +276,16 @@ export default class TxBlockScene extends TxMondrianPoolScene {
|
||||
alpha: 1
|
||||
}
|
||||
},
|
||||
duration: 500,
|
||||
delay: 0,
|
||||
state: 'fadeout'
|
||||
start: now,
|
||||
duration: 1500,
|
||||
delay: 50,
|
||||
state: 'fadeout',
|
||||
smooth: true,
|
||||
})
|
||||
}
|
||||
|
||||
prepareAll () {
|
||||
const now = performance.now()
|
||||
this.resize({})
|
||||
this.scene.count = 0
|
||||
let ids = this.getHiddenTxList()
|
||||
@ -258,7 +295,7 @@ export default class TxBlockScene extends TxMondrianPoolScene {
|
||||
}
|
||||
ids = this.getActiveTxList()
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
this.prepareTx(this.txs[ids[i]], this.scene.count++)
|
||||
this.prepareTx(this.txs[ids[i]], now)
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,30 +314,43 @@ export default class TxBlockScene extends TxMondrianPoolScene {
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
resetScroll () {
|
||||
return
|
||||
}
|
||||
|
||||
hide () {
|
||||
this.hidden = true
|
||||
const now = performance.now()
|
||||
const ids = this.getActiveTxList()
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
this.hideTx(this.txs[ids[i]])
|
||||
this.hideTx(this.txs[ids[i]], now)
|
||||
}
|
||||
}
|
||||
|
||||
show () {
|
||||
if (this.hidden) {
|
||||
this.hidden = false
|
||||
const now = performance.now()
|
||||
const ids = this.getActiveTxList()
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
this.showTx(this.txs[ids[i]])
|
||||
this.showTx(this.txs[ids[i]], now)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expire (delay=3000) {
|
||||
this.expired = true
|
||||
const txIds = this.getTxList()
|
||||
for (let i = 0; i < txIds.length; i++) {
|
||||
if (this.txs[txIds[i]]) {
|
||||
this.controller.deleteTx(txIds[i])
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
const txIds = this.getTxList()
|
||||
for (let i = 0; i < txIds.length; i++) {
|
||||
if (this.txs[txIds[i]]) this.controller.destroyTx(txIds[i])
|
||||
if (this.txs[txIds[i]]) {
|
||||
this.txs[txIds[i]].destroy()
|
||||
}
|
||||
}
|
||||
this.layout.destroy()
|
||||
}, delay)
|
||||
|
@ -30,8 +30,8 @@ export default class TxMondrianPoolScene extends TxPoolScene {
|
||||
else return logTxSize(tx.value, this.blockWidth)
|
||||
}
|
||||
|
||||
place (tx, index, size) {
|
||||
// console.log(`placing tx at ${index} (size ${size})`)
|
||||
place (tx) {
|
||||
const size = this.txSize(tx)
|
||||
const position = this.layout.place(tx, size)
|
||||
tx.gridPosition.x = position.x
|
||||
tx.gridPosition.y = position.y
|
||||
|
@ -22,6 +22,7 @@ export default class TxPoolScene {
|
||||
y: 0
|
||||
}
|
||||
}
|
||||
this.lastScroll = performance.now()
|
||||
|
||||
this.resize({ width, height })
|
||||
this.txs = {}
|
||||
@ -79,15 +80,19 @@ export default class TxPoolScene {
|
||||
|
||||
insert (tx, insertDelay, autoLayout=true) {
|
||||
if (autoLayout) {
|
||||
this.layoutTx(tx, this.scene.count++, insertDelay)
|
||||
this.txs[tx.id] = tx
|
||||
this.place(tx)
|
||||
if (this.checkTxScroll(tx)) {
|
||||
this.applyTxScroll(tx)
|
||||
}
|
||||
this.setTxOnScreen(tx, insertDelay)
|
||||
} else {
|
||||
this.hiddenTxs[tx.id] = tx
|
||||
}
|
||||
}
|
||||
|
||||
clearOffscreenTx (tx) {
|
||||
if (tx.pixelPosition && (tx.pixelPosition.y + tx.pixelPosition.r) < -(this.scene.offset.y + 20)) {
|
||||
if (tx.pixelPosition && (tx.pixelPosition.y + tx.pixelPosition.r) < -(this.scene.offset.y + 100)) {
|
||||
this.controller.destroyTx(tx.id)
|
||||
}
|
||||
}
|
||||
@ -97,6 +102,7 @@ export default class TxPoolScene {
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
this.clearOffscreenTx(this.txs[ids[i]])
|
||||
}
|
||||
this.clearTimer = null
|
||||
}
|
||||
|
||||
redrawTx (tx, now) {
|
||||
@ -108,37 +114,40 @@ export default class TxPoolScene {
|
||||
position: tx.screenPosition
|
||||
},
|
||||
duration: 1000,
|
||||
minDuration: 500,
|
||||
start: now,
|
||||
minDuration: 250,
|
||||
delay: 50,
|
||||
smooth: true,
|
||||
adjust: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
updateChunk (ids) {
|
||||
const now = performance.now()
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
this.redrawTx(this.txs[ids[i]], now)
|
||||
}
|
||||
}
|
||||
// updateChunk (ids, now = performance.now()) {
|
||||
// for (let i = 0; i < ids.length; i++) {
|
||||
// this.redrawTx(this.txs[ids[i]], now)
|
||||
// }
|
||||
// }
|
||||
|
||||
async doScroll (offset) {
|
||||
const ids = this.getTxList()
|
||||
this.scene.scroll += offset
|
||||
const processingChunks = []
|
||||
// Scrolling operation is potentially very costly, as we're calculating and updating positions of every active tx in the pool
|
||||
// So attempt to spread the cost over several frames, by separately processing chunks of 100 txs each
|
||||
// Schedule ~1 chunk per frame at the targeted framerate (60 fps, frame every 16.7ms)
|
||||
for (let i = 0; i < ids.length; i+=100) {
|
||||
processingChunks.push(new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
this.updateChunk(ids.slice(i, i+100))
|
||||
resolve()
|
||||
}, (i / 100) * 20)
|
||||
}))
|
||||
const now = performance.now()
|
||||
if (now - this.lastScroll > 1000) {
|
||||
this.lastScroll = now
|
||||
const ids = this.getTxList()
|
||||
this.scene.scroll += offset
|
||||
this.maxHeight += offset
|
||||
if (this.heightStore) this.heightStore.set(this.maxHeight)
|
||||
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
this.redrawTx(this.txs[ids[i]], now)
|
||||
}
|
||||
|
||||
if (!this.clearTimer) {
|
||||
this.clearTimer = setTimeout(() => {
|
||||
this.clearOffscreenTxs()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
await Promise.all(processingChunks)
|
||||
this.clearOffscreenTxs()
|
||||
}
|
||||
|
||||
scroll (offset, force) {
|
||||
@ -159,9 +168,7 @@ export default class TxPoolScene {
|
||||
return 1
|
||||
}
|
||||
|
||||
layoutTx (tx, sequence, insertDelay, setOnScreen = true) {
|
||||
const units = this.txSize(tx)
|
||||
this.place(tx, sequence, units)
|
||||
checkTxScroll (tx, insertDelay, setOnScreen = true) {
|
||||
this.saveGridToPixelPosition(tx)
|
||||
const top = tx.pixelPosition.y + tx.pixelPosition.r
|
||||
const bottom = tx.pixelPosition.y - tx.pixelPosition.r
|
||||
@ -169,16 +176,17 @@ export default class TxPoolScene {
|
||||
this.maxHeight = top
|
||||
if (this.heightStore) this.heightStore.set(this.maxHeight)
|
||||
}
|
||||
if (this.heightLimit && bottom > this.heightLimit) {
|
||||
this.scroll(this.heightLimit - bottom)
|
||||
this.maxHeight += (this.heightLimit - bottom)
|
||||
if (this.heightStore) this.heightStore.set(this.maxHeight)
|
||||
this.saveGridToPixelPosition(tx)
|
||||
}
|
||||
if (setOnScreen) this.setTxOnScreen(tx, insertDelay)
|
||||
return (this.heightLimit && bottom > this.heightLimit)
|
||||
}
|
||||
|
||||
applyTxScroll (tx) {
|
||||
const bottom = tx.pixelPosition.y - tx.pixelPosition.r
|
||||
this.scroll(this.heightLimit - bottom)
|
||||
}
|
||||
|
||||
setTxOnScreen (tx, insertDelay=0) {
|
||||
this.saveGridToPixelPosition(tx)
|
||||
this.savePixelsToScreenPosition(tx)
|
||||
if (!tx.view.initialised) {
|
||||
const txColor = tx.getColor(this.sceneType, this.colorMode)
|
||||
tx.view.update({
|
||||
@ -198,7 +206,7 @@ export default class TxPoolScene {
|
||||
})
|
||||
tx.view.update({
|
||||
display: {
|
||||
position: this.pixelsToScreen(tx.pixelPosition),
|
||||
position: tx.screenPosition,
|
||||
color: txColor.color
|
||||
},
|
||||
duration: 2500,
|
||||
@ -218,11 +226,13 @@ export default class TxPoolScene {
|
||||
} else {
|
||||
tx.view.update({
|
||||
display: {
|
||||
position: this.pixelsToScreen(tx.pixelPosition)
|
||||
position: tx.screenPosition
|
||||
},
|
||||
duration: 1500,
|
||||
minDuration: 1000,
|
||||
delay: 0,
|
||||
duration: 1000,
|
||||
minDuration: 500,
|
||||
delay: 50,
|
||||
jitter: 500,
|
||||
smooth: true,
|
||||
adjust: true
|
||||
})
|
||||
}
|
||||
@ -239,33 +249,37 @@ export default class TxPoolScene {
|
||||
}
|
||||
ids = this.getActiveTxList()
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
this.layoutTx(this.txs[ids[i]], this.scene.count++)
|
||||
this.place(this.txs[ids[i]])
|
||||
this.saveGridToPixelPosition(this.txs[ids[i]])
|
||||
}
|
||||
|
||||
this.resetScroll()
|
||||
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
this.setTxOnScreen(this.txs[ids[i]])
|
||||
}
|
||||
}
|
||||
|
||||
resetScroll () {
|
||||
const ids = this.getActiveTxList()
|
||||
let poolTop = -Infinity
|
||||
let poolBottom = Infinity
|
||||
let poolScreenTop = -Infinity
|
||||
ids = this.getActiveTxList()
|
||||
let tx
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
tx = this.txs[ids[i]]
|
||||
this.saveGridToPixelPosition(tx)
|
||||
// this.saveGridToPixelPosition(tx)
|
||||
poolTop = Math.max(poolTop, tx.pixelPosition.y - tx.pixelPosition.r)
|
||||
poolScreenTop = Math.max(poolScreenTop, tx.pixelPosition.y + tx.pixelPosition.r)
|
||||
poolBottom = Math.min(poolBottom, tx.pixelPosition.y - tx.pixelPosition.r)
|
||||
}
|
||||
|
||||
this.maxHeight = poolScreenTop
|
||||
let scrollAmount = Math.min(-this.scene.scroll, this.heightLimit - poolTop)
|
||||
|
||||
this.scene.scroll += scrollAmount
|
||||
this.maxHeight += scrollAmount
|
||||
|
||||
if (this.heightLimit && poolTop > this.heightLimit) {
|
||||
let scrollAmount = this.heightLimit - poolTop
|
||||
this.scroll(scrollAmount, true)
|
||||
this.maxHeight += scrollAmount
|
||||
} else if (this.heightLimit && poolTop < this.heightLimit) {
|
||||
let scrollAmount = Math.min(-this.scene.scroll, this.heightLimit - poolTop)
|
||||
this.scroll(scrollAmount, true)
|
||||
this.maxHeight += scrollAmount
|
||||
}
|
||||
if (this.heightStore) this.heightStore.set(this.maxHeight)
|
||||
}
|
||||
|
||||
@ -343,7 +357,8 @@ export default class TxPoolScene {
|
||||
return grid
|
||||
}
|
||||
|
||||
place (tx, position, size) {
|
||||
place (tx) {
|
||||
const size = this.txSize(tx)
|
||||
tx.gridPosition.x = 1 + Math.floor(position % this.blockWidth)
|
||||
tx.gridPosition.y = 1 + (Math.floor(position / this.blockWidth))
|
||||
tx.gridPosition.r = size
|
||||
|
@ -1,30 +1,42 @@
|
||||
import { smootherstep } from '../utils/easing.js'
|
||||
|
||||
function interpolateAttributeStart(attribute, now, modular) {
|
||||
if (attribute.v == 0 || (attribute.t + attribute.d) <= now) {
|
||||
// transition finished, next transition starts from current end state
|
||||
// (clamp to 1)
|
||||
attribute.a = attribute.b
|
||||
attribute.v = 0
|
||||
attribute.d = 0
|
||||
if (attribute.boom) {
|
||||
attribute.a = attribute.a
|
||||
attribute.v = 0
|
||||
attribute.d = 0
|
||||
} else {
|
||||
attribute.a = attribute.b
|
||||
attribute.v = 0
|
||||
attribute.d = 0
|
||||
}
|
||||
return false
|
||||
} else if (attribute.t > now) {
|
||||
// transition not started
|
||||
// (clamp to 0)
|
||||
return true
|
||||
} else {
|
||||
// transition in progress
|
||||
// (interpolate)
|
||||
let progress = (now - attribute.t)
|
||||
const progress = (now - attribute.t)
|
||||
const delta = attribute.e ? smootherstep(progress / attribute.d) : (progress / attribute.d)
|
||||
if (modular && Math.abs(attribute.a - attribute.b) > 0.5) {
|
||||
if (attribute.a > 0.5) {
|
||||
attribute.a -= 1
|
||||
attribute.a = attribute.a + ((progress / attribute.d) * (attribute.b - attribute.a))
|
||||
attribute.a = attribute.a + (delta * (attribute.b - attribute.a))
|
||||
} else {
|
||||
attribute.a = attribute.a + ((progress / attribute.d) * (attribute.b - 1 - attribute.a))
|
||||
attribute.a = attribute.a + (delta * (attribute.b - 1 - attribute.a))
|
||||
}
|
||||
if (attribute.a < 0) attribute.a += 1
|
||||
} else {
|
||||
attribute.a = attribute.a + ((progress / attribute.d) * (attribute.b - attribute.a))
|
||||
attribute.a = attribute.a + (delta * (attribute.b - attribute.a))
|
||||
}
|
||||
attribute.d = attribute.d - progress
|
||||
attribute.v = 1 / attribute.d
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,12 +51,12 @@ export default class TxSprite {
|
||||
}
|
||||
|
||||
this.attributes = {
|
||||
x: { a: x, b: x, t: offsetTime, v: 0, d: 0 },
|
||||
y: { a: y, b: y, t: offsetTime, v: 0, d: 0 },
|
||||
r: { a: r, b: r, t: offsetTime, v: 0, d: 0 },
|
||||
h: { a: h, b: h, t: offsetTime, v: 0, d: 0 },
|
||||
l: { a: l, b: l, t: offsetTime, v: 0, d: 0 },
|
||||
a: { a: alpha, b: alpha, t: offsetTime, v: 0, d: 0 },
|
||||
x: { a: x, b: x, t: 0, v: 0, d: 0 },
|
||||
y: { a: y, b: y, t: 0, v: 0, d: 0 },
|
||||
r: { a: r, b: r, t: 0, v: 0, d: 0 },
|
||||
h: { a: h, b: h, t: 0, v: 0, d: 0 },
|
||||
l: { a: l, b: l, t: 0, v: 0, d: 0 },
|
||||
a: { a: alpha, b: alpha, t: 0, v: 0, d: 0 },
|
||||
}
|
||||
|
||||
// Used to temporarily modify the sprite, so that the base view can be resumed later
|
||||
@ -55,15 +67,16 @@ export default class TxSprite {
|
||||
this.compile()
|
||||
}
|
||||
|
||||
interpolateAttributes (updateMap, attributes, offsetTime, v, duration, minDuration, adjust) {
|
||||
interpolateAttributes (updateMap, attributes, offsetTime, delay, v, smooth, boomerang, duration, minDuration, adjust) {
|
||||
for (const key of Object.keys(updateMap)) {
|
||||
// for each non-null attribute:
|
||||
if (updateMap[key] != null) {
|
||||
// calculate current interpolated value, and set as 'from'
|
||||
interpolateAttributeStart(attributes[key], offsetTime, key === 'h')
|
||||
const inProgress = interpolateAttributeStart(attributes[key], offsetTime, key === 'h')
|
||||
// interpolateAttributeStart(attributes[key], offsetTime, key)
|
||||
// set 'start' to now
|
||||
attributes[key].t = offsetTime
|
||||
if (!adjust || !inProgress) attributes[key].t += (delay || 0)
|
||||
// if 'adjust' flag set
|
||||
// set 'duration' to Max(remaining time, 'duration')
|
||||
if (!adjust || (duration && attributes[key].d == 0)) {
|
||||
@ -75,11 +88,18 @@ export default class TxSprite {
|
||||
}
|
||||
// set 'to' to target value
|
||||
attributes[key].b = updateMap[key]
|
||||
|
||||
if (!adjust || !inProgress) {
|
||||
if (smooth) attributes[key].e = true
|
||||
else if (!smooth && attributes[key].e) delete attributes[key].e
|
||||
if (boomerang) attributes[key].boom = true
|
||||
else if (!boomerang && attributes[key].boom) delete attributes[key].boom
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update ({ now = performance.now(), x, y, r, h, l, alpha, duration, minDuration, adjust, modify }) {
|
||||
update ({ now = performance.now(), delay, x, y, r, h, l, alpha, smooth, boomerang, duration, minDuration, adjust, modify }) {
|
||||
const offsetTime = now
|
||||
const v = duration > 0 ? (1 / duration) : 0
|
||||
|
||||
@ -92,7 +112,7 @@ export default class TxSprite {
|
||||
|
||||
const isModified = !!this.modAttributes
|
||||
if (!modify) {
|
||||
this.interpolateAttributes(this.updateMap, this.attributes, offsetTime, v, duration, minDuration, adjust)
|
||||
this.interpolateAttributes(this.updateMap, this.attributes, offsetTime, delay, v, smooth, boomerang, duration, minDuration, adjust)
|
||||
} else {
|
||||
if (!isModified) { // set up the modAttributes
|
||||
this.modAttributes = {}
|
||||
@ -102,7 +122,7 @@ export default class TxSprite {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.interpolateAttributes(this.updateMap, this.modAttributes, offsetTime, v, duration, minDuration, adjust)
|
||||
this.interpolateAttributes(this.updateMap, this.modAttributes, offsetTime, delay, v, smooth, boomerang, duration, minDuration, adjust)
|
||||
}
|
||||
|
||||
this.compile()
|
||||
@ -152,7 +172,18 @@ export default class TxSprite {
|
||||
for (let step = 0; step < VI.length; step++) {
|
||||
// components of each field in the vertex array are defined by an entry in VI:
|
||||
// VI[i].a is the attribute, VI[i].f is the inner field, VI[i].offA and VI[i].offB are offset factors
|
||||
this.vertexData[(vertex * vertexStride) + step + 2] = attributes[VI[step].a][VI[step].f]
|
||||
if (VI[step].f === 'v' && attributes[VI[step].a].e) {
|
||||
if (VI[step].f === 'v' && attributes[VI[step].a].boom) {
|
||||
this.vertexData[(vertex * vertexStride) + step + 2] = -20 - attributes[VI[step].a][VI[step].f]
|
||||
}
|
||||
else {
|
||||
this.vertexData[(vertex * vertexStride) + step + 2] = -attributes[VI[step].a][VI[step].f]
|
||||
}
|
||||
} else if (VI[step].f === 'v' && attributes[VI[step].a].boom) {
|
||||
this.vertexData[(vertex * vertexStride) + step + 2] = -10 - attributes[VI[step].a][VI[step].f]
|
||||
} else {
|
||||
this.vertexData[(vertex * vertexStride) + step + 2] = attributes[VI[step].a][VI[step].f]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,13 +4,17 @@ const highlightTransitionTime = 300
|
||||
|
||||
// converts from this class's update format to TxSprite's update format
|
||||
// now, id, value, position, size, color, alpha, duration, adjust
|
||||
function toSpriteUpdate(display, duration, delay, start, adjust) {
|
||||
function toSpriteUpdate(display, duration, minDuration, delay, start, adjust, smooth, boomerang) {
|
||||
return {
|
||||
now: (start || performance.now()) + (delay || 0),
|
||||
now: (start || performance.now()),
|
||||
delay: delay,
|
||||
duration: duration,
|
||||
minDuration: minDuration,
|
||||
...(display.position ? display.position: {}),
|
||||
...(display.color ? display.color: {}),
|
||||
adjust
|
||||
adjust,
|
||||
smooth,
|
||||
boomerang
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,14 +50,14 @@ export default class TxView {
|
||||
delay: for queued transitions, how long to wait after current transition
|
||||
completes to start.
|
||||
*/
|
||||
update ({ display, duration, delay, jitter, state, start, adjust }) {
|
||||
update ({ display, duration, minDuration, delay = 0, jitter, state, start, adjust, smooth, boomerang }) {
|
||||
this.state = state
|
||||
if (jitter) delay += (Math.random() * jitter)
|
||||
|
||||
if (!this.initialised || !this.sprite) {
|
||||
this.initialised = true
|
||||
this.sprite = new TxSprite(
|
||||
toSpriteUpdate(display, duration, delay, start),
|
||||
toSpriteUpdate(display, duration, minDuration, delay, start, adjust, smooth, boomerang),
|
||||
this.vertexArray
|
||||
)
|
||||
// apply any pending modifications
|
||||
@ -74,7 +78,7 @@ export default class TxView {
|
||||
}
|
||||
} else {
|
||||
this.sprite.update(
|
||||
toSpriteUpdate(display, duration, delay, start, adjust)
|
||||
toSpriteUpdate(display, duration, minDuration, delay, start, adjust, smooth, boomerang)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -87,7 +91,7 @@ export default class TxView {
|
||||
...this.hoverColor,
|
||||
duration: highlightTransitionTime,
|
||||
adjust: false,
|
||||
modify: true
|
||||
modify: true,
|
||||
})
|
||||
} else {
|
||||
this.hover = false
|
||||
|
@ -15,9 +15,40 @@ uniform vec2 screenSize;
|
||||
uniform float now;
|
||||
uniform sampler2D colorTexture;
|
||||
|
||||
float interpolate(float x, bool useSmooth, bool boomerang) {
|
||||
x = clamp(x, 0.0, 1.0);
|
||||
if (boomerang) {
|
||||
x = 2.0 * x;
|
||||
if (x > 1.0) {
|
||||
x = 2.0 - x;
|
||||
}
|
||||
}
|
||||
if (useSmooth) {
|
||||
float ix = 1.0 - x;
|
||||
x = x * x;
|
||||
return x / (x + ix * ix);
|
||||
} else {
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
// hue is modular, so interpolation should take the shortest path, wrapping around if necessary
|
||||
float interpolateHue(vec4 hue) {
|
||||
float delta = clamp((now - hue.z) * hue.w, 0.0, 1.0);
|
||||
bool useSmooth = false;
|
||||
bool boomerang = false;
|
||||
if (hue.w <= -20.0) {
|
||||
boomerang = true;
|
||||
useSmooth = true;
|
||||
hue.w = (0.0 - hue.w) - 20.0;
|
||||
} else if (hue.w <= -10.0) {
|
||||
boomerang = true;
|
||||
hue.w = (0.0 - hue.w) - 10.0;
|
||||
} else if (hue.w < 0.0) {
|
||||
useSmooth = true;
|
||||
hue.w = -hue.w;
|
||||
}
|
||||
float d = clamp((now - hue.z) * hue.w, 0.0, 1.0);
|
||||
float delta = interpolate(d, useSmooth, boomerang);
|
||||
if (abs(hue.x - hue.y) > 0.5) {
|
||||
if (hue.x > 0.5) {
|
||||
return mod(mix(hue.x - 1.0, hue.y, delta), 1.0);
|
||||
@ -30,7 +61,21 @@ float interpolateHue(vec4 hue) {
|
||||
}
|
||||
|
||||
float interpolateAttribute(vec4 attr) {
|
||||
float delta = clamp((now - attr.z) * attr.w, 0.0, 1.0);
|
||||
bool useSmooth = false;
|
||||
bool boomerang = false;
|
||||
if (attr.w <= -20.0) {
|
||||
boomerang = true;
|
||||
useSmooth = true;
|
||||
attr.w = (0.0 - attr.w) - 20.0;
|
||||
} else if (attr.w <= -10.0) {
|
||||
boomerang = true;
|
||||
attr.w = (0.0 - attr.w) - 10.0;
|
||||
} else if (attr.w < 0.0) {
|
||||
useSmooth = true;
|
||||
attr.w = -attr.w;
|
||||
}
|
||||
float d = clamp((now - attr.z) * attr.w, 0.0, 1.0);
|
||||
float delta = interpolate(d, useSmooth, boomerang);
|
||||
return mix(attr.x, attr.y, delta);
|
||||
}
|
||||
|
||||
|
@ -4,3 +4,9 @@ export function easeOutBack(x) {
|
||||
|
||||
return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2);
|
||||
}
|
||||
|
||||
export function smootherstep(x) {
|
||||
const ix = 1 - x
|
||||
x = x * x
|
||||
return x / (x + ix * ix)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user