Animating Gutenberg Blocks with Vue.js: A Deep Dive into CSS Animations
Gutenberg, WordPress’s block editor, empowers users to create dynamic and visually appealing websites. While Gutenberg provides some styling capabilities, extending its visual richness often requires custom JavaScript development. This blog post will delve into a powerful technique: controlling CSS animations within Gutenberg blocks using Vue.js. We’ll build a custom block with a configurable animation applied through a Vue.js component, demonstrating a robust and maintainable solution.
Why Vue.js and CSS Animations?
Using Vue.js to manage CSS animations in Gutenberg offers several significant advantages:
- Data Reactivity: Vue.js’s reactivity system effortlessly updates the animation’s properties (e.g., duration, delay, easing) whenever the user changes settings within the block’s settings panel. No manual DOM manipulation is required.
- Component-Based Architecture: We can encapsulate the animation logic within a reusable Vue component, promoting code organization and maintainability. This makes the code easier to test and extend.
- Clean Separation of Concerns: Vue.js handles the data and logic while CSS focuses solely on the visual presentation of the animation. This separation enhances code readability and reduces complexity.
- Improved User Experience: Users can easily customize the animations without needing deep knowledge of CSS or JavaScript.
Setting up the Environment
Before we start, ensure you have Node.js and npm (or yarn) installed. We’ll use the @wordpress/scripts
package to build our Gutenberg block.
- Create a new project:
mkdir vue-gutenberg-animation
cd vue-gutenberg-animation
npm init -y
- Install necessary packages:
npm install @wordpress/scripts @wordpress/element vue
- Create
package.json
scripts:
Add the following scripts to your package.json
:
{
"scripts": {
"build": "wp-scripts build",
"start": "wp-scripts start"
}
}
Building the Vue Component
Let’s create a Vue component (src/components/AnimatedText.vue
) responsible for rendering and animating the text:
<template>
<div class="animated-text" :class="{ 'animate': animate }" :style="animationStyles">
<slot />
</div>
</template>
<script>
export default {
name: 'AnimatedText',
props: {
animation: {
type: String,
default: 'fadeIn',
},
duration: {
type: Number,
default: 1000,
},
delay: {
type: Number,
default: 0,
},
easing: {
type: String,
default: 'ease',
},
animate: {
type: Boolean,
default: true,
}
},
computed: {
animationStyles() {
return {
animationDuration: `${this.duration}ms`,
animationDelay: `${this.delay}ms`,
animationTimingFunction: this.easing,
};
},
},
};
</script>
<style scoped>
.animated-text {
opacity: 0; /* Initial state before animation */
transition: opacity 0.3s ease; /* Smooth transition for initial render */
}
.animated-text.animate {
animation-name: {{ animation }};
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* Add other animation keyframes here */
@keyframes slideInLeft {
from { transform: translateX(-100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes bounceIn {
from { transform: scale(0.3); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
</style>
This component accepts animation
, duration
, delay
, and easing
properties. It dynamically applies CSS classes and inline styles based on these props, creating a reactive animation. We define several keyframes for different animation effects, but you can easily add more. The animate
prop controls whether the animation is running or not; this is useful for controlling animation on initial render.
Creating the Gutenberg Block
Now, let’s create the Gutenberg block (src/blocks/animated-text/index.js
):
import { registerBlockType } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import Edit from './edit';
import save from './save';
import './editor.css';
import AnimatedText from '../../components/AnimatedText.vue';
registerBlockType('my-plugin/animated-text', {
title: __('Animated Text'),
icon: 'format-quote',
category: 'common',
attributes: {
text: { type: 'string', default: 'Animated Text' },
animation: { type: 'string', default: 'fadeIn' },
duration: { type: 'number', default: 1000 },
delay: { type: 'number', default: 0 },
easing: { type: 'string', default: 'ease' },
},
edit: Edit,
save,
});
The Edit
component (src/blocks/animated-text/edit.js
) will handle the block’s editor interface using Vue.js:
import { __ } from '@wordpress/i18n';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, TextControl, SelectControl } from '@wordpress/components';
import { useState, useRef, useEffect } from '@wordpress/element';
import { render } from 'vue';
import AnimatedText from '../../components/AnimatedText.vue';
const Edit = (props) => {
const { attributes, setAttributes } = props;
const { text, animation, duration, delay, easing } = attributes;
const blockRef = useRef(null);
useEffect(() => {
const vueInstance = render(
h(AnimatedText, {
animation: animation,
duration: duration,
delay: delay,
animate: true // start animation on mount
}, [text]),
blockRef.current,
);
return () => vueInstance.unmount();
}, [attributes]);
const blockProps = useBlockProps();
return (
<>
<InspectorControls>
<PanelBody title={__('Animation Settings')}>
<SelectControl
label={__('Animation')}
value={animation}
options={[
{ label: 'Fade In', value: 'fadeIn' },
{ label: 'Slide In Left', value: 'slideInLeft' },
{ label: 'Bounce In', value: 'bounceIn' },
]}
onChange={(value) => setAttributes({ animation: value })}
/>
<TextControl
label={__('Duration (ms)')}
value={duration}
onChange={(value) => setAttributes({ duration: parseInt(value, 10) })}
/>
<TextControl
label={__('Delay (ms)')}
value={delay}
onChange={(value) => setAttributes({ delay: parseInt(value, 10) })}
/>
<SelectControl
label={__('Easing')}
value={easing}
options={[
{ label: 'Ease', value: 'ease' },
{ label: 'Ease-in', value: 'ease-in' },
{ label: 'Ease-out', value: 'ease-out' },
{ label: 'Ease-in-out', value: 'ease-in-out' },
]}
onChange={(value) => setAttributes({ easing: value })}
/>
</PanelBody>
</InspectorControls>
<div {...blockProps} ref={blockRef}>
</div>
</>
);
};
export default Edit;
The save
function (src/blocks/animated-text/save.js
) is simple:
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
const save = (props) => {
const { attributes } = props;
const { text, animation, duration, delay, easing } = attributes;
const blockProps = useBlockProps.save();
return (
<div {...blockProps}>
<div className="animated-text" style={{ animationDuration: `${duration}ms`, animationDelay: `${delay}ms`, animationTimingFunction: easing }}>
{text}
</div>
</div>
);
};
export default save;
Finally, you need to add some basic CSS in src/blocks/animated-text/editor.css
:
.wp-block-my-plugin-animated-text {
width: 100%;
}
This setup renders a simple text block with configurable animation properties. The user can select the animation type, duration, delay, and easing function through the Gutenberg inspector panel. The Vue component handles the actual animation dynamically.
Building and Running
Run the following commands to build and start the development server:
npm run build
npm run start
This will start a local development server, allowing you to test the block within the WordPress editor.
Conclusion
This detailed guide demonstrates how to effectively leverage Vue.js to control CSS animations within Gutenberg blocks. By combining Vue.js’s reactive capabilities with CSS animations, we create a highly dynamic and user-friendly block that enhances the visual appeal of WordPress websites. This approach promotes code maintainability, readability, and a superior user experience. Remember to adapt and extend this code to incorporate your desired animations and block functionalities. This approach sets a strong foundation for building complex and visually stunning Gutenberg blocks. Further enhancements could include adding more animation options, improving error handling, and integrating with animation libraries like Animate.css for even greater flexibility.
Leave a Reply