June 25, 2024
pomber
Rodrigo Pombo
@pomber

From markdown to video

Animated code walkthroughs with Code Hike and Remotion

For a long time, many people have asked for a way to combine Code Hike and Remotion to turn code walkthroughs into videos. Because of Code Hike limitations, this wasn't possible without a lot of hacks. Until now.

This changes with Code Hike v1.

With the new version of Code Hike you can now use custom react components for everything. Also, v1 allows you to pass structured markdown content to these components in any shape you want.

The process from markdown to video is something like this:

  1. Use Code Hike to pass annotated markdown content (and codeblocks) to your components.
  2. In your components, use Code Hike and Remotion to animate annotations and transitions.
  3. Use Remotion to render your components into videos.

Let's see how this works.

In this example I'm not including the project configuration. If you want to follow along, you can find the full code in the Code Hike examples repository.

Rendering steps from Markdown

We start by importing a markdown file:

content.md
## !!steps One
Lorem
## !!steps Two
Ipsum

Then we can use Code Hike's parseRoot to trasform the markdown into a list:

const steps = [
{
title: "One",
children: <p>Lorem</p>,
},
{
title: "Two",
children: <p>Ipsum</p>,
},
]

We can pass those steps to a Video component and render a Sequence for each step:

<AbsoluteFill>
<Sequence
title="One"
from={0}
durationInFrames={60}
>
<p>Lorem</p>
</Sequence>
<Sequence
title="Two"
from={60}
durationInFrames={60}
>
<p>Ipsum</p>
</Sequence>
</AbsoluteFill>

This tells Remotion to render Lorem for the first 60 frames and then Ipsum from frame 60 to frame 120.

The output:

root.jsx
import Content from "./content.md"
import {
parseRoot,
Block,
} from "code-hike/blocks"
import { z } from "zod"
const Schema = Block.extend({
steps: z.array(Block),
})
const { steps } = parseRoot(
Content,
Schema,
)
import {
AbsoluteFill,
Sequence,
Composition,
} from "remotion"
const STEP_FRAMES = 60
function Video({ steps }) {
return (
<AbsoluteFill
style={{ background: "#0D1117" }}
>
{steps.map((step, i) => (
<Sequence
layout="none"
name={step.title}
from={STEP_FRAMES * i}
durationInFrames={STEP_FRAMES}
>
{step.children}
</Sequence>
))}
</AbsoluteFill>
)
}
export function RemotionRoot() {
return (
<Composition
id="CodeHikeExample"
component={Video}
defaultProps={{ steps }}
width={300}
height={200}
fps={60}
durationInFrames={
STEP_FRAMES * steps.length
}
/>
)
}

So far nothing too fancy.

Rendering Codeblocks

Things get interesting when we start adding annotated codeblocks.

content.md
## !!steps One
```js !
let lorem = 1
lorem += 2
```
## !!steps Two
```js !
let lorem = 1
if (ipsum) {
lorem += 2
}
```

We can change the content and the Schema to include codeblocks.

Now each step from steps will have a code property.

Rendering code

We can use Code Hike's Pre component to render the code.

Now the output video shows each codeblock for one second:

Adding annotations

We can add annotations to the codeblocks:

content.md
## !!steps One
```js !
let lorem = 1
lorem += 2
```
## !!steps Two
```js !
// !mark
let lorem = 1
if (ipsum) {
// !mark
lorem += 2
}
```

And then define an annotation handler to tell Code Hike what component should use to render those annotations.

For more examples and inspiration of what you can do with annotations, check the code examples.

Animating annotations

Since annotations are handled by React components, we can use Remotion hooks inside them.

For example, we can use the current frame together with interpolateColors to add a fade-in effect.

This is the output:

The frame is relative to the current <Sequence />, so the fade-in effect happens from the 10th frame to the 20th frame of the second sequence.

We can also make the delay and duration of the annotations parametrizable, by using the annotation query.

content.md
## !!steps One
```js !
let lorem = 1
lorem += 2
```
## !!steps Two
```js !
// !mark 10
let lorem = 1
if (ipsum) {
// !mark 25
lorem += 2
}
```

annotation.query is the string after the annotation name. In this case, "10" and "25".

Animating the code

Finally, we can use Code Hike's token transition utils to make the transition between the codeblocks more interesting:

root.jsx
import Content from "./content.md"
const Schema = Block.extend({
steps: z.array(
Block.extend({
code: HighlightedCodeBlock,
}),
),
})
const { steps } = parseRoot(
Content,
Schema,
)
const STEP_FRAMES = 60
function Video({ steps }) {
return (
<AbsoluteFill
style={{
background: "#0D1117",
alignItems: "center",
}}
>
{steps.map((step, i) => (
<Sequence
layout="none"
name={step.title}
from={STEP_FRAMES * i}
durationInFrames={STEP_FRAMES}
>
<Code code={step.code} />
</Sequence>
))}
</AbsoluteFill>
)
}
export function RemotionRoot() {
return (
<Composition
id="CodeHikeExample"
component={Video}
defaultProps={{ steps }}
durationInFrames={
STEP_FRAMES * steps.length
}
fps={60}
width={140}
height={90}
/>
)
}

Examples

You can find the full code in the Code Hike examples repository.

The repository also includes some extra examples: