Embed Animations in Blog Posts Using MDX

Ifeoma Imoh

Markdown is a plain text formatting syntax used to write content on the web. Markdown’s intuitive and concise syntax makes it easier to write readable and maintainable content that can be converted to structurally valid HTML. While it shines at static content, adding interactivity to Markdown content has been a major challenge. With the advent of MDX, this is no longer a problem because it is now possible to embed interactivity and dynamic content.

MDX is a superset of markdown that allows you to write JSX directly in your markdown files, bringing life to your pages. In this article, I will show you how to add animations (using anime.js) to your blog posts using MDX and Next.js.

For this article, you will need Node.js version 10.13 or later.

Here is a link to the demo CodeSandbox.

Project Setup

Run this command in your terminal to create a Next.js app in a folder called mdx_demo:

1npx create-next-app mdx-demo

In addition to the React and Next base dependencies, our project will need the following dependencies:

  • MDX Plugins: @next/mdx and @mdx-js/loader will help with parsing code, transforming HTML elements and so on.
  • Animejs: We will use this for our custom animation.

Add the project dependencies using the following command:

1npm i @next/mdx @mdx-js/loader animejs

Once this command completes execution, update your next.config.js to match the following:

1const withMDX = require('@next/mdx')();
2 module.exports = withMDX({
3 pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
4 });

The @next/mdx package is configured in the next.config.js file at the root of your project. It sources data from local files, allowing you to create pages with a .mdx extension directly in your /pages directory.

Create Animation Component

Let’s create a simple animation to be rendered on the MDX page. Create a new folder called components at the root of your project. In this folder, create a new file called Animation.jsx and add the following code to it:

1import { useEffect, useRef, useState } from 'react';
2 import anime from 'animejs';
3 const Animation = () => {
4 const circles = [...Array(101).keys()];
5 const colours = [
6 'yellow',
7 'powderblue',
8 'green',
9 'black',
10 '#3300CC',
11 '#FF0000',
12 '#00CC33',
13 '#660099',
14 '#FF6600',
15 '#000033',
16 '#990033',
17 ];
18 const refs = useRef([]);
19 const [windowHeight, setWindowHeight] = useState(undefined);
20 const [windowWidth, setWindowWidth] = useState(undefined);
21 useEffect(() => {
22 setWindowHeight(window.innerHeight);
23 setWindowWidth(window.innerWidth);
24 }, []);
25 const animation = () => {
26 anime({
27 targets: refs.current,
28 translateY: () => anime.random(0, windowHeight / 2),
29 translateX: () => anime.random(0, windowWidth / 2),
30 easing: 'easeInOutSine',
31 duration: 5000,
32 loop: true,
33 direction: 'alternate',
34 complete: animation,
35 });
36 };
37 useEffect(() => {
38 animation();
39 });
40 const getRandomNumber = (min, max) => Math.random() * (max - min) + min;
41 const randomTop = () => getRandomNumber(0, windowHeight / 2);
42 const randomLeft = () => getRandomNumber(0, windowWidth / 2);
43 return circles.map((key, index) => (
44 <div
45 key={key}
46 ref={(el) => (refs.current[index] = el)}
47 style={{
48 position: 'absolute',
49 top: `${randomTop()}px`,
50 left: `${randomLeft()}px`,
51 width: 50,
52 height: 50,
53 backgroundColor: colours[Math.floor(Math.random() * colours.length)],
54 borderRadius: '50%',
55 opacity: 0.2,
56 }}
57 ></div>
58 ));
59 };
60 export default Animation;

This component renders 100 circles of varied colors at random positions on the page and applies a simple animation to each. We generate random values for the translateX and translateY attributes of the anime function, causing the circles to move around the page. See here for more animations using Animejs.

Because we will only be able to access the Window interface client-side, we use the useEffect hook to set the window height and width once the component is rendered.

Update Index Page

Since we want to use MDX pages, we can delete the index.js file in the pages directory. In its place, create a new file called index.mdx and add the following code to it:

1import Animation from '../components/Animation';
2 <Animation />
3 ![MDX + Next Image](https://i.ibb.co/zPgN0NN/Markdown-Logo.png)
4 # Embedding Animations in Your Blog Posts Using MDX and Next.js
5 ## What is Markdown?
6 Markdown is a lightweight markup language that you can use to add formatting elements to plaintext text documents.
7 Created by [John Gruber](https://daringfireball.net/projects/markdown/) in 2004, Markdown is now one of the world’s most popular markup languages.
8 You can add Markdown formatting elements to a plaintext file using a text editor application.
9 Or you can use one of the many Markdown applications for macOS, Windows, Linux, iOS, and Android operating systems.
10 There are also several web-based applications specifically designed for writing in Markdown.
11 Depending on the application you use, you may not be able to preview the formatted document in real time.
12 But that’s okay. According to Gruber, Markdown syntax is designed to be readable and unobtrusive,
13 so the text in Markdown files can be read even if it isn’t rendered.
14 You can read more about Markdown [here](https://www.markdownguide.org/)
15 ## Why MDX?
16 [MDX](https://mdxjs.com/docs/what-is-mdx/) takes Markdown a step further and allows you to use JSX in your markdown content.
17 You can import components, such as interactive charts or alerts and embed them within your content.
18 This makes writing long-form content with components a blast. 🚀
19 Version 2 of MDX was released after years of hard work and has many improvements. Here are the highlights:
20 - 📝 Improved syntax makes it easier to use markdown in JSX
21 - 🧑‍💻 JavaScript expressions turn {2 \* Math.PI} into 6.283185307179586
22 - 🔌 New esbuild, Rollup, and Node.js integrations
23 - ⚛️ Any JSX runtime: React, Preact, Vue, Emotion, you name it, they’re all supported
24 - 🌳 Improved AST exposes more info in greater detail
25 - 🏃‍♀️ Compiles at least 25% faster
26 - 🚴 Generated code runs twice as fast (100% faster)
27 - 🚄 Bundle size of @mdx-js/mdx is more than three times as small (250% smaller)
28 - 🧵 …and much, [so much more](https://mdxjs.com/blog/v2/)

At the top of this file, we import our earlier declared Animation component and then render it before declaring the remaining Markdown content.

With this in place, our application is ready to run but before we do that, let’s make some styling modifications.

First, we’ll modify the JSX of the default App component to add some padding for all the components. Update your pages/_app.js to match the following:

1import '../styles/globals.css';
2 function MyApp({ Component, pageProps }) {
3 return (
4 <div style={{ padding: '2% 10%' }}>
5 <Component {...pageProps} />
6 </div>
7 );
8 }
9 export default MyApp;

Next, in styles/global.css, update the styling for the a element to match the following:

1a {
2 color: blue;
3 }

With this in place, you can run the application using the following command.

1npm run dev

By default, the application will be served at http://localhost:3000/. Navigating to the URL shows the rendered page, and the final result will look like the gif below.

Find the complete project here on GitHub.

Conclusion

In this article, we looked at a simple example of how we can embed animations into blog posts using MDX and Next.js. Embedding React components into Markdown makes the content dynamic and allows us to have content that responds to user interactions, making the interactions even for users.

Resources you may find helpful:

Ifeoma Imoh

Software Developer

Ifeoma is a software developer and technical content creator in love with all things JavaScript.