Token Transitions

Animate the transition between code blocks at a token level.

content.md
```scala
object Main {
def factorial(n: Int): Int = {
if (n == 0) {
return 1
} else {
return n * factorial(n - 1)
}
}
}
```
```python
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n - 1)
```
object Main {
def factorial(n: Int): Int = {
if (n == 0) {
return 1
} else {
return n * factorial(n - 1)
}
}
}

Implementation

We are going to use some utils from codehike/utils/token-transitions. The easiest way of using them is inside a class component:

smooth-pre.tsx
"use client"
import { CustomPreProps, InnerPre, getPreRef } from "codehike/code"
import {
TokenTransitionsSnapshot,
calculateTransitions,
getStartingSnapshot,
} from "codehike/utils/token-transitions"
import React from "react"
const MAX_TRANSITION_DURATION = 900 // milliseconds
export class SmoothPre extends React.Component<CustomPreProps> {
ref: React.RefObject<HTMLPreElement>
constructor(props: CustomPreProps) {
super(props)
this.ref = getPreRef(this.props)
}
render() {
return <InnerPre merge={this.props} style={{ position: "relative" }} />
}
getSnapshotBeforeUpdate() {
return getStartingSnapshot(this.ref.current!)
}
componentDidUpdate(
prevProps: never,
prevState: never,
snapshot: TokenTransitionsSnapshot,
) {
const transitions = calculateTransitions(this.ref.current!, snapshot)
transitions.forEach(({ element, keyframes, options }) => {
const { translateX, translateY, ...kf } = keyframes as any
if (translateX && translateY) {
kf.translate = [
`${translateX[0]}px ${translateY[0]}px`,
`${translateX[1]}px ${translateY[1]}px`,
]
}
element.animate(kf, {
duration: options.duration * MAX_TRANSITION_DURATION,
delay: options.delay * MAX_TRANSITION_DURATION,
easing: options.easing,
fill: "both",
})
})
}
}

Then we create the annotation handler:

code.tsx
import { AnnotationHandler, InnerToken } from "codehike/code"
import { SmoothPre } from "./smooth-pre"
export const tokenTransitions: AnnotationHandler = {
name: "token-transitions",
PreWithRef: SmoothPre,
Token: (props) => (
<InnerToken merge={props} style={{ display: "inline-block" }} />
),
}

And then pass the handler to the Pre component:

page.tsx
async function Code({ codeblock }: { codeblock: RawCode }) {
const highlighted = await highlight(codeblock, "github-dark")
return <Pre code={highlighted} handlers={[tokenTransitions]} />
}

When the codeblock changes, the Code component will animate the transition between the old and new code blocks.

See it in action