社区新闻

如何创建动画时间轴插件

查看官方原文 ↗ 发布于

你是否见过动态的事件时间轴?当访问者向下滚动,新事件进入视口时,时间轴会将任何额外的事件以动画形式呈现出来。像时间轴这样的线性视觉元素可以讲述一个故事。这些事件可能代表个人或公司历史上的关键里程碑。同样,动画可以帮助强化叙事,并吸引访问者的创意兴趣。

我将引导你创建一个独特的时间轴体验。你将学习如何使用区块构建它,注册一些用于动画的自定义CSS,并使用JavaScript来观察视口中相交的元素。当然,并非每个人都希望体验页面上的元素移动,因此我们将向你展示如何选择关闭动画。

首先,你将使用区块构建时间轴的布局和外观。我最终使用了以下区块:

  • 一个分配了自定义时间轴CSS类的群组区块
    • 一个包含三个嵌套列的列区块
      1. 第一个列区块代表事件的日期。例如:“2024年4月2日”
      2. 第二个列区块代表视觉时间轴分隔线。我使用了区块锁定功能,以防止编辑器意外删除或移动此区块。
      3. 第三个列区块是时间轴上每个事件内容所在的位置。

接下来,考虑修改图案的内部内容以分配关键的动画类,这将在你的插件中完成。

动画时间轴插件设置

让我们创建一个插件来打包所有内容。这也将允许你重复使用它并将其放入任何项目中。

首先注册你的插件并设置文件结构。在你的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_blockrender_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.jsgroup--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;
	}
}

你可能注意到我为你包含了两种时间轴样式变体:

  1. 默认animated-timeline: 默认外观和样式。
  2. 圆形animated-timeline animated-timeline--circles: 将分隔线装饰更改为圆形、点状处理。

你可以使用你喜欢的任何一种,甚至可以创建新的变体。只需确保在编辑器的高级 -> 附加CSS类字段中添加animated-timelineanimated-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并自定义它。你可以在评论区分享你在项目中如何使用它。