Skip to content

Commit 6d9d9a5

Browse files
🚀 RELEASE: let’s do it! 🕺
0 parents  commit 6d9d9a5

File tree

9 files changed

+431
-0
lines changed

9 files changed

+431
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Operating system specific files
2+
.DS_Store

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Animated timeline plugin
2+
3+
This WordPress plugin extends the core Group block to create an animated timeline experience with subtle animation and considerations for [`prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) respecting a visitors preference for non-essential motion.
4+
5+
The final pattern is registered within the plugin, but it could also potentially be included directly in a custom WordPress theme. It can also be found on the WordPress Pattern Directory as [Vertical Timeline](https://wordpress.org/patterns/pattern/vertical-timeline/).
6+
7+
There are two possible styling variations: `animated-timeline` and `animated-timeline animated-timeline--circles`.
8+
9+
## How to use
10+
11+
1. Download this plugin as a zip (click on 'Code' and choose 'Download ZIP')
12+
2. Place the un-zipped directory in your WordPress `wp-content/plugins` directory
13+
3. Activate the plugin
14+
4. Create a new post / page and add the 'Animated Timeline' pattern.
15+
5. Save and preview the final animated timeline.
16+
6. Try editing the Advanced -> Additional CSS Classes from `animated-timeline` to `animated-timeline animated-timeline--circles` for the overall pattern's parent Group block to see the circle variation.
17+
18+
Feel free to fork it and use it however you like!
19+
20+
## Changelog
21+
22+
### June 11, 2024 - v1.0.0
23+
24+
Initial launch.

animated-timeline.php

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
/**
3+
* Plugin Name: Animated Timeline
4+
* Description: Extends the Group block for an animating timeline effect.
5+
* Requires at least: 6.5
6+
* Requires PHP: 7.4
7+
* Version: 1.0.0
8+
* Author: Damon Cook
9+
* License: GPL-2.0-or-later
10+
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
11+
* Text Domain: animated-timeline
12+
*/
13+
14+
if ( ! defined( 'ABSPATH' ) ) {
15+
exit; // Exit if accessed directly.
16+
}
17+
18+
19+
/**
20+
* Registers the custom script and style for the timeline plugin.
21+
*/
22+
function animated_timeline_register_scripts() {
23+
24+
// Register the custom script to be used later.
25+
wp_register_script(
26+
'animated-timeline-script',
27+
plugin_dir_url( __FILE__ ) . '/assets/scripts/core-blocks/group--animated-timeline.js',
28+
array(),
29+
'1.0.0',
30+
true
31+
);
32+
33+
// Register the custom style to be used later.
34+
wp_register_style(
35+
'animated-timeline-style',
36+
plugin_dir_url( __FILE__ ) . '/assets/styles/core-blocks/group--animated-timeline.css',
37+
array(),
38+
'1.0.0',
39+
);
40+
}
41+
add_action( 'wp_enqueue_scripts', 'animated_timeline_register_scripts' );
42+
43+
/**
44+
* Modify the core Group block.
45+
*
46+
* @param string $block_content The block content about to be rendered.
47+
*
48+
* @return string The maybe modified block content.
49+
*/
50+
function animated_timeline_filter_group_content( $block_content ) {
51+
$processor = new WP_HTML_Tag_Processor( $block_content );
52+
$counter = 0;
53+
54+
// Check for the presence of the 'timeline' class.
55+
if ( ! $processor->next_tag( array( 'class_name' => 'animated-timeline' ) ) ) {
56+
return $block_content;
57+
}
58+
59+
// Loop through each child block with the class name 'wp-block-column'.
60+
while ( $processor->next_tag( array( 'class_name' => 'wp-block-column' ) ) ) {
61+
$processor->add_class( 'animated__item' );
62+
++$counter;
63+
64+
switch ( $counter ) {
65+
case 1:
66+
$processor->add_class( 'animated__item--first' );
67+
break;
68+
case 2:
69+
$processor->add_class( 'animated__item--line' );
70+
break;
71+
case 3:
72+
$processor->add_class( 'animated__item--last' );
73+
$counter = 0;
74+
break;
75+
}
76+
}
77+
78+
$block_content = $processor->get_updated_html();
79+
80+
// Enqueue the custom script and style.
81+
wp_enqueue_script( 'animated-timeline-script' );
82+
wp_enqueue_style( 'animated-timeline-style' );
83+
84+
// Return the maybe modified block content.
85+
return $block_content;
86+
}
87+
add_filter( 'render_block_core/group', 'animated_timeline_filter_group_content', 10 );
88+
89+
/**
90+
* Registers a block pattern for the timeline plugin.
91+
*
92+
* 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.
93+
*/
94+
function animated_timeline_register_block_pattern() {
95+
$pattern_file = plugin_dir_path( __FILE__ ) . '/patterns/animated-timeline.php';
96+
97+
if ( ! file_exists( $pattern_file ) ) {
98+
return;
99+
}
100+
101+
register_block_pattern(
102+
'animated-timeline/animated-timeline',
103+
require $pattern_file
104+
);
105+
}
106+
add_action( 'init', 'animated_timeline_register_block_pattern' );

assets/images/WP-6.3-Lionel.png

209 KB
Loading

assets/images/WP-6.4-Shirley.png

213 KB
Loading

assets/images/WP-6.5-Regina.png

194 KB
Loading
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
/**
3+
* Initializes an Intersection Observer to add the 'loaded' class to elements when they become visible in the viewport.
4+
* The Intersection Observer is set up to observe elements with the class 'animated__item'.
5+
*
6+
* @listens DOMContentLoaded
7+
*/
8+
document.addEventListener( 'DOMContentLoaded', () => {
9+
const els = document.querySelectorAll( '.animated__item' );
10+
11+
const observerOptions = {
12+
root: null,
13+
rootMargin: '0px',
14+
threshold: 0.33,
15+
};
16+
17+
/**
18+
* Callback function for the Intersection Observer.
19+
* Adds the 'loaded' class to the target element if it is intersecting.
20+
*
21+
* @param {IntersectionObserverEntry[]} entries - An array of IntersectionObserverEntry objects.
22+
*/
23+
function observerCallback( entries ) {
24+
entries.forEach( ( entry ) => {
25+
if ( entry.isIntersecting ) {
26+
entry.target.classList.add( 'loaded' );
27+
}
28+
} );
29+
}
30+
31+
const observer = new IntersectionObserver(
32+
observerCallback,
33+
observerOptions
34+
);
35+
36+
els.forEach( ( el ) => observer.observe( el ) );
37+
} );
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/* Establish the positioning context for the timeline. */
2+
.animated-timeline > div {
3+
position: relative;
4+
}
5+
6+
.animated__item {
7+
visibility: hidden;
8+
}
9+
10+
.animated__item.loaded {
11+
visibility: visible;
12+
}
13+
14+
/* First column - initial state */
15+
.animated-timeline .animated__item--first {
16+
opacity: 0;
17+
transform: translateY(25px);
18+
transition: transform 0.5s, opacity 0.5s;
19+
transition-delay: 0.3s;
20+
}
21+
22+
/* Last column - initial state */
23+
.animated-timeline .animated__item--last {
24+
opacity: 0;
25+
transform: translateY(45px);
26+
transition: transform 0.7s, opacity 0.8s;
27+
transition-delay: 0.5s;
28+
}
29+
30+
/* First and last column - loaded state */
31+
.animated-timeline .animated__item--first.loaded,
32+
.animated-timeline .animated__item--last.loaded {
33+
opacity: 1;
34+
transform: translateY(0);
35+
}
36+
37+
/**
38+
* Vertical line animation
39+
* The vertical line is a pseudo-element of the middle column.
40+
*/
41+
42+
/* Establish positioning context */
43+
.animated-timeline .animated__item--line {
44+
position: relative;
45+
visibility: hidden;
46+
}
47+
48+
/* Vertical line - initial state */
49+
.animated-timeline .animated__item--line::before {
50+
background-color: inherit;
51+
content: "";
52+
display: block;
53+
height: 1px;
54+
inset: 0;
55+
opacity: 0;
56+
overflow: hidden;
57+
position: absolute;
58+
transition: height 1.5s, opacity 0.1s;
59+
transition-delay: 0.1s;
60+
transition-origin: top;
61+
visibility: hidden;
62+
width: 100%;
63+
z-index: -1;
64+
}
65+
66+
/* Vertical line - loaded state */
67+
.animated-timeline .animated__item--line.loaded::before {
68+
height: 100%;
69+
opacity: 1;
70+
visibility: visible;
71+
}
72+
73+
/* Middle column - inner text - initial state */
74+
.animated-timeline .animated__item--line > p {
75+
opacity: 0;
76+
transition: opacity 0.5s;
77+
transition-delay: 1.15s;
78+
visibility: hidden;
79+
}
80+
81+
/* Middle column - inner text - loaded state */
82+
.animated-timeline:not(.animated-timeline--circles) .animated__item--line.loaded > p {
83+
opacity: 1;
84+
visibility: visible;
85+
}
86+
87+
/**
88+
* Circle timeline
89+
* The circle timeline is a variation of the default timeline.
90+
*/
91+
.animated-timeline--circles .animated__item--line > p {
92+
opacity: 1;
93+
position: relative;
94+
visibility: hidden;
95+
}
96+
97+
/* Create the circles */
98+
.animated-timeline--circles .animated__item--line > p::after,
99+
.animated-timeline--circles .animated__item--line > p::before {
100+
background-color: inherit;
101+
border-radius: 50%;
102+
content: "";
103+
display: block;
104+
height: 1rem;
105+
left: calc(50% - 0.5rem);
106+
opacity: 0;
107+
position: absolute;
108+
top: calc(50% - 0.5rem);
109+
transition: opacity 0.4s, transform 0.6s;
110+
visibility: hidden;
111+
width: 1rem;
112+
}
113+
114+
/* Background circle - initial state */
115+
.animated-timeline--circles .animated__item--line > p::after {
116+
background: none;
117+
box-shadow: 0 0 0 4px currentColor;
118+
transform: scale(0);
119+
transition-delay: 1.2s;
120+
z-index: 1;
121+
}
122+
123+
/* Background circle - loaded state */
124+
.animated-timeline--circles .animated__item--line.loaded > p::after {
125+
opacity: 0.4;
126+
transform: scale(1);
127+
visibility: visible;
128+
}
129+
130+
/* Foreground circle - initial state */
131+
.animated-timeline--circles .animated__item--line > p::before {
132+
transition-delay: 1s;
133+
z-index: 2;
134+
}
135+
136+
/* Foreground circle - loaded state */
137+
.animated-timeline--circles .animated__item--line.loaded > p::before {
138+
opacity: 1;
139+
visibility: visible;
140+
}
141+
142+
@media (prefers-reduced-motion: reduce) {
143+
.animated-timeline *,
144+
.animated-timeline *::after,
145+
.animated-timeline *::before {
146+
opacity: 1 !important;
147+
transition: none !important;
148+
visibility: visible !important;
149+
}
150+
}

0 commit comments

Comments
 (0)