如何创建动画时间轴插件
你是否见过动态的事件时间轴?当访问者向下滚动,新事件进入视口时,时间轴会将任何额外的事件以动画形式呈现出来。像时间轴这样的线性视觉元素可以讲述一个故事。这些事件可能代表个人或公司历史上的关键里程碑。同样,动画可以帮助强化叙事,并吸引访问者的创意兴趣。
我将引导你创建一个独特的时间轴体验。你将学习如何使用区块构建它,注册一些用于动画的自定义CSS,并使用JavaScript来观察视口中相交的元素。当然,并非每个人都希望体验页面上的元素移动,因此我们将向你展示如何选择关闭动画。
首先,你将使用区块构建时间轴的布局和外观。我最终使用了以下区块:
- 一个分配了自定义时间轴CSS类的群组区块。
- 一个包含三个嵌套列的列区块。
- 第一个列区块代表事件的日期。例如:“2024年4月2日”
- 第二个列区块代表视觉时间轴分隔线。我使用了区块锁定功能,以防止编辑器意外删除或移动此区块。
- 第三个列区块是时间轴上每个事件内容所在的位置。
- 一个包含三个嵌套列的列区块。
接下来,考虑修改图案的内部内容以分配关键的动画类,这将在你的插件中完成。
动画时间轴插件设置
让我们创建一个插件来打包所有内容。这也将允许你重复使用它并将其放入任何项目中。
首先注册你的插件并设置文件结构。在你的wp-content/plugins目录中创建一个新的animated-timeline目录,并添加所有需要的文件:
animated-timeline/: 你的插件文件夹/assets/scripts/core-blocks/group--animated-timeline.js: 你将使用JavaScript观察视口中相交元素的地方。/assets/styles/core-blocks/group--animated-timeline.css: 你将使用CSS来过渡、延迟和动画关键元素的地方。animated-timeline.php: 主插件文件,你将在此放置大部分钩子。
现在,在animated-timeline.php文件中添加你的WordPress插件头部:
<?php
/**
* Plugin Name: Animated Timeline
* Description: Extends the Group block for an animating timeline effect.
* Requires at least: 6.5
* Requires PHP: 7.4
* Version: 1.0.0
* License: GPL-2.0-or-later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: animated-timeline
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
继续并激活你的新插件。
HTML标签处理器和区块过滤器
你需要完成的一个关键部分是过滤群组区块的内部内容。你需要检查是否有任何群组区块分配了自定义类,确保它是.animated-timeline,然后处理其内部区块内容。
你可以使用以下方便的过滤器来修改区块的输出:render_block和render_block_{$this->name}。然而,由于目标是针对群组区块的自定义,你可以使用动态的render_block_core/group变体。
以下是将添加到你的插件animated-timeline.php文件中的代码:
/**
* Modify the core Group block.
*
* @param string $block_content The block content about to be rendered.
*
* @return string The maybe modified block content.
*/
function animated_timeline_filter_group_content( $block_content ) {
$processor = new WP_HTML_Tag_Processor( $block_content );
$counter = 0;
// Check for the presence of the animated-timeline class.
if ( ! $processor->next_tag( array( 'class_name' => 'animated-timeline' ) ) ) {
return $block_content;
}
// Loop through each child block with the class name 'wp-block-column'.
while ( $processor->next_tag( array( 'class_name' => 'wp-block-column' ) ) ) {
$processor->add_class( 'animated__item' );
++$counter;
switch ( $counter ) {
case 1:
$processor->add_class( 'animated__item--first' );
break;
case 2:
$processor->add_class( 'animated__item--line' );
break;
case 3:
$processor->add_class( 'animated__item--last' );
$counter = 0;
break;
}
}
$block_content = $processor->get_updated_html();
// Enqueue the custom script and style.
wp_enqueue_script( 'animated-timeline-script' );
wp_enqueue_style( 'animated-timeline-style' );
// Return the maybe modified block content.
return $block_content;
}
add_filter( 'render_block_core/group', 'animated_timeline_filter_group_content', 10 );
HTML标签处理器是一个强大的工具,可以帮助循环遍历嵌套的区块内容并为每个列区块分配类。你可能还注意到代码使用了wp_enqueue_script()和wp_enqueue_style()来排队自定义CSS和JavaScript文件,但前提是带有.timeline类的群组区块位于网站前端。这确保了仅当带有.animated-timeline类的群组区块在页面上输出时才加载资源。
注册自定义CSS和JavaScript文件
在上一步中,你调用了wp_enqueue_script( 'animated-timeline-script' )和wp_enqueue_style( 'animated-timeline-style' )。你需要注册这些JavaScript和CSS文件,以便WordPress知道要加载什么。将以下代码添加到你的animated-timeline.php中,以注册group--animated-timeline.js和group--animated-timeline.css文件:
/**
* Registers the custom script and style for the timeline plugin.
*/
function animated_timeline_register_scripts() {
// Register the custom script to be used later.
wp_register_script(
'animated-timeline-script',
plugin_dir_url( __FILE__ ) . '/assets/scripts/core-blocks/group--animated-timeline.js',
array(),
'1.0.0',
true
);
// Register the custom style to be used later.
wp_register_style(
'animated-timeline-style',
plugin_dir_url( __FILE__ ) . '/assets/styles/core-blocks/group--animated-timeline.css',
array(),
'1.0.0',
);
}
add_action( 'wp_enqueue_scripts', 'animated_timeline_register_scripts' );
添加动画样式
现在将自定义CSS添加到/assets/styles/core-blocks/group--animated-timeline.css文件中以处理动画:
/* Establish the positioning context for the timeline. */
.animated-timeline > div {
position: relative;
}
.animated__item {
visibility: hidden;
}
.animated__item.loaded {
visibility: visible;
}
/* First column - initial state */
.animated-timeline .animated__item--first {
opacity: 0;
transform: translateY(25px);
transition: transform 0.5s, opacity 0.5s;
transition-delay: 0.3s;
}
/* Last column - initial state */
.animated-timeline .animated__item--last {
opacity: 0;
transform: translateY(45px);
transition: transform 0.7s, opacity 0.8s;
transition-delay: 0.5s;
}
/* First and last column - loaded state */
.animated-timeline .animated__item--first.loaded,
.animated-timeline .animated__item--last.loaded {
opacity: 1;
transform: translateY(0);
}
/**
* Vertical line animation
* The vertical line is a pseudo-element of the middle column.
*/
/* Establish positioning context */
.animated-timeline .animated__item--line {
position: relative;
visibility: hidden;
}
/* Vertical line - initial state */
.animated-timeline .animated__item--line::before {
background-color: inherit;
content: "";
display: block;
height: 1px;
inset: 0;
opacity: 0;
overflow: hidden;
position: absolute;
transition: height 1.5s, opacity 0.1s;
transition-delay: 0.1s;
transition-origin: top;
visibility: hidden;
width: 100%;
z-index: -1;
}
/* Vertical line - loaded state */
.animated-timeline .animated__item--line.loaded::before {
height: 100%;
opacity: 1;
visibility: visible;
}
/* Middle column - inner text - initial state */
.animated-timeline .animated__item--line > p {
opacity: 0;
transition: opacity 0.5s;
transition-delay: 1.15s;
visibility: hidden;
}
/* Middle column - inner text - loaded state */
.animated-timeline:not(.animated-timeline--circles) .animated__item--line.loaded > p {
opacity: 1;
visibility: visible;
}
/**
* Circle timeline
* The circle timeline is a variation of the default timeline.
*/
.animated-timeline--circles .animated__item--line > p {
opacity: 1;
position: relative;
visibility: hidden;
}
/* Create the circles */
.animated-timeline--circles .animated__item--line > p::after,
.animated-timeline--circles .animated__item--line > p::before {
background-color: inherit;
border-radius: 50%;
content: "";
display: block;
height: 1rem;
left: calc(50% - 0.5rem);
opacity: 0;
position: absolute;
top: calc(50% - 0.5rem);
transition: opacity 0.4s, transform 0.6s;
visibility: hidden;
width: 1rem;
}
/* Background circle - initial state */
.animated-timeline--circles .animated__item--line > p::after {
background: none;
box-shadow: 0 0 0 4px currentColor;
transform: scale(0);
transition-delay: 1.2s;
z-index: 1;
}
/* Background circle - loaded state */
.animated-timeline--circles .animated__item--line.loaded > p::after {
opacity: 0.4;
transform: scale(1);
visibility: visible;
}
/* Foreground circle - initial state */
.animated-timeline--circles .animated__item--line > p::before {
transition-delay: 1s;
z-index: 2;
}
/* Foreground circle - loaded state */
.animated-timeline--circles .animated__item--line.loaded > p::before {
opacity: 1;
visibility: visible;
}
@media (prefers-reduced-motion: reduce) {
.animated-timeline *,
.animated-timeline *::after,
.animated-timeline *::before {
opacity: 1 !important;
transition: none !important;
visibility: visible !important;
}
}
你可能注意到我为你包含了两种时间轴样式变体:
- 默认 –
animated-timeline: 默认外观和样式。 - 圆形 –
animated-timeline animated-timeline--circles: 将分隔线装饰更改为圆形、点状处理。
你可以使用你喜欢的任何一种,甚至可以创建新的变体。只需确保在编辑器的高级 -> 附加CSS类字段中添加animated-timeline或animated-timeline animated-timeline--circles。
最后一个CSS块包含一个@media (prefers-reduced-motion)声明。这允许访问者利用其设备设置来最小化任何非必要的运动。
添加动画JavaScript
你已经有了用于动画不同类的样式。现在你需要添加一点JavaScript来观察视口的滚动,并将类附加到目标元素以进行动画处理。
将以下代码放在/assets/styles/core-blocks/group--animated-timeline.js文件中:
/**
* Initializes an Intersection Observer to add the 'loaded' class to elements when they become visible in the viewport.
* The Intersection Observer is set up to observe elements with the class 'animated__item'.
*
* @listens DOMContentLoaded
*/
document.addEventListener( 'DOMContentLoaded', () => {
const els = document.querySelectorAll( '.animated__item' );
const observerOptions = {
root: null,
rootMargin: '0px',
threshold: 0.33,
};
/**
* Callback function for the Intersection Observer.
* Adds the 'loaded' class to the target element if it is intersecting.
*
* @param {IntersectionObserverEntry[]} entries - An array of IntersectionObserverEntry objects.
*/
function observerCallback( entries ) {
entries.forEach( ( entry ) => {
if ( entry.isIntersecting ) {
entry.target.classList.add( 'loaded' );
}
} );
}
const observer = new IntersectionObserver(
observerCallback,
observerOptions
);
els.forEach( ( el ) => observer.observe( el ) );
} );
上述代码利用JavaScript的IntersectionObserver API来观察视口中相交的元素,并将.loaded类附加到任何目标.animated__item元素。
区块图案注册
你现在应该已经让动画时间轴工作了。然而,还需要考虑如何组织你的文件。请记住,最终功能依赖于你的插件(按目前状态)和一个区块图案。有几种可能包含图案的方式:
- 在你的插件中注册并包含它(推荐)。
- 在自定义主题中注册并包含它(只需将图案的副本放在你主题的
patterns/目录中)。 - 在创建新文章/页面时根据需要从图案目录添加它。
如果你更愿意在最终插件中注册图案,这是你应该放在animated-timeline.php文件中的内容:
/**
* Registers a block pattern for the timeline plugin.
*
* This function registers a block pattern for the timeline plugin. It checks if the pattern file exists and then registers the pattern using the `register_block_pattern` function.
*/
function animated_timeline_register_block_pattern() {
$pattern_file = plugin_dir_path( __FILE__ ) . '/patterns/animated-timeline.php';
if ( ! file_exists( $pattern_file ) ) {
return;
}
register_block_pattern(
'animated-timeline/animated-timeline',
require $pattern_file
);
}
add_action( 'init', 'animated_timeline_register_block_pattern' );
上面的animated_timeline_register_block_pattern()函数正在检查/patterns/animated-timeline.php图案,你需要将其包含在你的插件中。你可以在此GitHub仓库上找到最终的animated-timeline.php图案。注意:该图案引用了一些图像,这些图像也相对于图案定位(参见GitHub仓库中的assets/images/)。为了完整性,你将需要包含这些图像。
总结
通过一点规划和正确的WordPress API,你可以进行大量自定义,并通过利用WordPress核心功能从未来的惊人新功能中受益。
我希望你学会了如何分解问题,并考虑如何应对下一个可能提供类似视觉组件的潜在客户项目。
这是包含完整插件代码的最终GitHub仓库:github.com/colorful-tones/animated-timeline-plugin。请随意fork并自定义它。你可以在评论区分享你在项目中如何使用它。