PluginProbe
Animations for Blocks / trunk
Animations for Blocks vtrunk
1.2.7 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.1.5 1.1.6 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6
animations-for-blocks / animations-for-blocks.php

animations-for-blocks.php in Animations for Blocks trunk, at animations-for-blocks.php

751 lines 19.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: Animations for Blocks
4 * Plugin URI: https://wordpress.org/plugins/animations-for-blocks
5 * Description: Allows to add animations to Gutenberg blocks on scroll.
6 * Version: 1.2.7
7 * Requires PHP: 8.1
8 * Author: skadev
9 * Author URI: https://profiles.wordpress.org/skadev/
10 */
11
12 namespace wsd\anfb;
13
14 use WP_HTML_Tag_Processor;
15 use WP_Block;
16
17 defined('ABSPATH') || exit;
18
19 function_exists('get_plugin_data') || require_once ABSPATH . 'wp-admin/includes/plugin.php';
20 $anfb_plugin = get_plugin_data(__FILE__, false, false);
21 define('WSD_ANFB_VER', $anfb_plugin['Version']);
22 define('WSD_ANFB_FILE', __FILE__);
23 define('WSD_ANFB_DIR', dirname(__FILE__));
24 define('WSD_ANFB_AOS_HANDLE', 'animate-on-scroll');
25
26
27 /**
28 * @return array Default plugin options value.
29 */
30 function get_default_settings() {
31 return [
32 'animateInEditor' => true,
33 'lazyloadAssets' => true,
34 'lenis' => 'off',
35 'location' => 'default',
36 'defaultAnimation' => [
37 'animation' => 'scale',
38 'variation' => 'in-x',
39 'delay' => 0,
40 'duration' => 800,
41 'once' => true,
42 'mirror' => false,
43 'easing' => 'ease-out-cubic',
44 'offset' => 120,
45 'anchorPlacement' => 'top-bottom',
46 ],
47 'ignoreReducedMotionPreference' => false,
48 ];
49 }
50
51
52 /**
53 * @return array Animation providers provide these contexts and any blocks with animations should consume them.
54 */
55 function get_animation_block_context_types() {
56 return [
57 /**
58 * @var bool `anfb/animation-container` block always provides contexts,
59 * but they should only be consumed when this is `true`, as in the
60 * "Animation container" block is actually in "provider mode".
61 */
62 'animationsForBlocksProvider',
63 /**
64 * @var array Provided animation configuration.
65 */
66 'animationsForBlocksAnimation',
67 /**
68 * @var number Added delay for every subsequent consumption.
69 */
70 'animationsForBlocksStagger',
71 ];
72 }
73
74
75 /**
76 * Register global plugin options.
77 */
78 function register_settings() {
79
80 $default_settings = get_default_settings();
81
82 register_setting(
83 'animations-for-blocks',
84 'animations-for-blocks',
85 [
86 'description' => __('Animations for Blocks settings', 'animations-for-blocks'),
87 'show_in_rest' => [
88 'schema' => [
89 'type' => 'object',
90 'properties' => [
91 'animateInEditor' => [
92 'type' => 'boolean',
93 'default' => $default_settings['animateInEditor'],
94 ],
95 'lazyloadAssets' => [
96 'type' => 'boolean',
97 'default' => $default_settings['lazyloadAssets'],
98 ],
99 'lenis' => [
100 'type' => 'string',
101 'enum' => ['off', 'on', 'animate'],
102 'default' => $default_settings['lenis'],
103 ],
104 'location' => [
105 'type' => 'string',
106 'enum' => ['default', 'advanced', 'styles'],
107 'default' => $default_settings['location'],
108 ],
109 'defaultAnimation' => [
110 'type' => 'object',
111 'properties' => [
112 'animation' => [
113 'type' => 'string',
114 'enum' => ['fade', 'flip', 'slide', 'zoom', 'scale', 'inherit', 'default'],
115 'default' => $default_settings['defaultAnimation']['animation'],
116 ],
117 'variation' => [
118 'type' => 'string',
119 'enum' => ['fade', 'up', 'down', 'left', 'right', 'up-left', 'up-right', 'down-left', 'down-right', 'in', 'in-up', 'in-down', 'in-left', 'in-right', 'out', 'out-up', 'out-down', 'out-left', 'out-right', 'in-x', 'in-y', 'out-x', 'out-y'],
120 'default' => $default_settings['defaultAnimation']['variation'],
121 ],
122 'delay' => [
123 'type' => 'number',
124 'default' => $default_settings['defaultAnimation']['delay'],
125 ],
126 'duration' => [
127 'type' => 'number',
128 'default' => $default_settings['defaultAnimation']['duration'],
129 ],
130 'once' => [
131 'type' => 'boolean',
132 'default' => $default_settings['defaultAnimation']['once'],
133 ],
134 'mirror' => [
135 'type' => 'boolean',
136 'default' => $default_settings['defaultAnimation']['mirror'],
137 ],
138 'easing' => [
139 'type' => 'string',
140 'enum' => ['ease', 'ease-in', 'ease-out', 'ease-in-out', 'ease-in-back', 'ease-out-back', 'ease-in-out-back', 'ease-in-sine', 'ease-out-sine', 'ease-in-out-sine', 'ease-in-quad', 'ease-out-quad', 'ease-in-out-quad', 'ease-in-cubic', 'ease-out-cubic', 'ease-in-out-cubic', 'ease-in-quart', 'ease-out-quart', 'ease-in-out-quart', 'linear'],
141 'default' => $default_settings['defaultAnimation']['easing'],
142 ],
143 'offset' => [
144 'type' => 'number',
145 'default' => $default_settings['defaultAnimation']['offset'],
146 ],
147 'anchorPlacement' => [
148 'type' => 'string',
149 'enum' => ['top-bottom', 'center-bottom', 'bottom-bottom', 'top-center', 'center-center', 'bottom-center', 'top-top', 'bottom-top', 'center-top'],
150 'default' => $default_settings['defaultAnimation']['anchorPlacement'],
151 ],
152 ],
153 'default' => $default_settings['defaultAnimation'],
154 ],
155 'ignoreReducedMotionPreference' => [
156 'type' => 'boolean',
157 'default' => $default_settings['ignoreReducedMotionPreference'],
158 ],
159 ],
160 'sanitize_callback' => function($settings) {
161 /** Has a strict schema limited to bool/number/enum to guarantee integrity, options should only be updated via REST API. */
162 return $settings;
163 },
164 ],
165 ],
166 'default' => $default_settings,
167 ]
168 );
169 }
170 add_action('init', __NAMESPACE__ . '\\register_settings');
171
172
173 /**
174 * @return array Blocks known to not work properly with Animations for Blocks.
175 */
176 function get_unsupported_blocks() {
177 return apply_filters('anfb_unsupported_blocks', [
178 'core/freeform',
179 'core/html',
180 'core/shortcode',
181 'core/legacy-widget',
182 ]);
183 }
184
185
186 /**
187 * Determine if block is supported to have an animation.
188 *
189 * @param string $block_name
190 * @return boolean
191 */
192 function is_supported($block_name) {
193
194 static $not_supported;
195 if(!is_array($not_supported)) {
196 $not_supported = get_unsupported_blocks();
197 }
198
199 return !in_array($block_name, $not_supported, true);
200 }
201
202
203 /**
204 * Register plugin assets.
205 *
206 * @see https://github.com/michalsnik/aos
207 */
208 function register_assets() {
209
210 $options = get_option('animations-for-blocks');
211
212 /** @var array */
213 $asset = include WSD_ANFB_DIR . '/build/index.asset.php';
214
215 wp_register_style(
216 WSD_ANFB_AOS_HANDLE,
217 plugins_url('build/aos.css', WSD_ANFB_FILE),
218 [],
219 $asset['version'], // 3.0.0-beta.6
220 ($options['ignoreReducedMotionPreference'] ?? false) ? 'screen' : 'screen and (prefers-reduced-motion: no-preference)'
221 );
222
223 wp_register_script(
224 WSD_ANFB_AOS_HANDLE,
225 plugins_url('build/aos.js', WSD_ANFB_FILE),
226 [],
227 $asset['version'], // 3.0.0-beta.6
228 ['in_footer' => true, 'strategy' => 'defer']
229 );
230
231 /** @var array */
232 $asset = include WSD_ANFB_DIR . '/build/lenis.asset.php';
233
234 wp_register_style(
235 'anfb/lenis',
236 plugins_url('build/lenis.css', WSD_ANFB_FILE),
237 [],
238 $asset['version'],
239 'all'
240 );
241
242 wp_register_script(
243 'anfb/lenis',
244 plugins_url('build/lenis.js', WSD_ANFB_FILE),
245 [],
246 $asset['version'],
247 ['in_footer' => true, 'strategy' => 'defer']
248 );
249
250 /** @var array */
251 $asset = include WSD_ANFB_DIR . '/build/init.asset.php';
252 wp_register_script(
253 'animations-for-blocks',
254 plugins_url('build/init.js', WSD_ANFB_FILE),
255 [
256 /** Use the filter below if your current setup already loads AOS. */
257 apply_filters('anfb_aos_handle', WSD_ANFB_AOS_HANDLE),
258 ],
259 $asset['version'],
260 ['in_footer' => true, 'strategy' => 'defer']
261 );
262 }
263 add_action('init', __NAMESPACE__ . '\\register_assets');
264
265
266 /**
267 * Enqueue editor assets.
268 */
269 function editor_assets() {
270
271 /** @var array */
272 $asset = include WSD_ANFB_DIR . '/build/index.asset.php';
273
274 wp_enqueue_style(
275 'animations-for-blocks-admin',
276 plugins_url('build/style-index.css', WSD_ANFB_FILE),
277 [],
278 $asset['version'],
279 'all'
280 );
281
282 wp_enqueue_script(
283 'animations-for-blocks-admin',
284 plugins_url('build/index.js', WSD_ANFB_FILE),
285 $asset['dependencies'],
286 $asset['version'],
287 false
288 );
289
290 wp_localize_script(
291 'animations-for-blocks-admin',
292 'anfbData',
293 [
294 'unsupportedBlocks' => get_unsupported_blocks(),
295 'settings' => get_option('animations-for-blocks'),
296 ]
297 );
298
299 wp_set_script_translations('animations-for-blocks', 'animations-for-blocks');
300 }
301 add_action('enqueue_block_editor_assets', __NAMESPACE__ . '\\editor_assets', 5);
302
303
304 /**
305 * Enqueue block assets (styles for `.editor-styles-wrapper`).
306 */
307 function block_assets() {
308
309 if(!is_admin()) {
310 return;
311 }
312
313 /** @var array */
314 $asset = include WSD_ANFB_DIR . '/build/index.asset.php';
315
316 wp_enqueue_style(
317 'animations-for-blocks-editor',
318 plugins_url('build/editor.css', WSD_ANFB_FILE),
319 [],
320 $asset['version'],
321 'all'
322 );
323 }
324 add_action('enqueue_block_assets', __NAMESPACE__ . '\\block_assets');
325
326
327 /**
328 * Enqueues front end AOS assets.
329 */
330 function enqueue_front_end_aos_assets() {
331
332 if(apply_filters('anfb_load_styles', true, 'aos')) {
333 /** Load Animate on Scroll styles. */
334 wp_enqueue_style(WSD_ANFB_AOS_HANDLE);
335 }
336
337 if(apply_filters('anfb_load_scripts', true, 'aos')) {
338 /** Initialize Animate on Scroll library. */
339 wp_enqueue_script('animations-for-blocks');
340 }
341 }
342
343
344 /**
345 * Enqueues front end Lenis assets.
346 */
347 function enqueue_front_end_lenis_assets() {
348
349 if(apply_filters('anfb_load_styles', true, 'lenis')) {
350 wp_enqueue_style('anfb/lenis');
351 }
352
353 if(apply_filters('anfb_load_scripts', true, 'lenis')) {
354 wp_enqueue_script('anfb/lenis');
355 }
356 }
357
358 /**
359 * Enqueue front end assets in head.
360 */
361 function front_end_assets() {
362
363 /** @var array */
364 $options = get_option('animations-for-blocks');
365
366 if(isset($options['lenis']) && $options['lenis'] === 'on') {
367 enqueue_front_end_lenis_assets();
368 }
369
370 if($options['lazyloadAssets']) {
371 return;
372 }
373
374 enqueue_front_end_aos_assets();
375 }
376 add_action('wp_enqueue_scripts', __NAMESPACE__ . '\\front_end_assets', 500);
377
378
379 /**
380 * AMP behavior.
381 *
382 * @param bool $load Load assets.
383 * @return bool
384 */
385 function disable_on_amp($load) {
386
387 if(function_exists('is_amp_endpoint') && is_amp_endpoint()) {
388 return false;
389 }
390
391 return $load;
392 }
393 add_filter('anfb_load_styles', __NAMESPACE__ . '\\disable_on_amp');
394 add_filter('anfb_load_scripts', __NAMESPACE__ . '\\disable_on_amp');
395
396
397 /**
398 * @param array $args Animation config.
399 * @return array Attributes to add to root element.
400 */
401 function get_animation_attributes($args = []) {
402
403 $args = wp_parse_args($args, [
404 'animation' => 'none',
405 'variation' => '',
406 'delay' => 0,
407 'duration' => 400,
408 'once' => false,
409 'mirror' => false,
410 'easing' => 'ease',
411 'offset' => 120,
412 'anchorPlacement' => 'top-bottom',
413 ]);
414
415 $attributes = [];
416
417 if(empty($args['animation']) || $args['animation'] === 'none') {
418 return $attributes;
419 }
420
421 /** Animation. */
422 $attributes['data-aos'] = $args['animation'] === $args['variation']
423 ? $args['animation']
424 : $args['animation'] . '-' . $args['variation'];
425
426 /** Delay. */
427 if(is_numeric($args['delay']) && (int)$args['delay'] !== 0) {
428 $attributes['data-aos-delay'] = (int)$args['delay'];
429 }
430
431 /** Duration. */
432 if(is_numeric($args['duration']) && (int)$args['duration'] !== 400) {
433 $attributes['data-aos-duration'] = (int)$args['duration'];
434 }
435
436 /** Easing. */
437 if(!empty($args['easing']) && $args['easing'] !== 'ease') {
438 $attributes['data-aos-easing'] = $args['easing'];
439 }
440
441 /** Once. */
442 if($args['once'] === 'true' || $args['once'] === true) {
443 $attributes['data-aos-once'] = 'true';
444 }
445
446 /** Mirror. */
447 if($args['mirror'] === 'true' || $args['mirror'] === true) {
448 $attributes['data-aos-mirror'] = 'true';
449 }
450
451 /** Offset. */
452 if(is_numeric($args['offset']) && (int)$args['offset'] !== 120) {
453 $attributes['data-aos-offset'] = (int)$args['offset'];
454 }
455
456 /** Anchor placement. */
457 if(!empty($args['anchorPlacement']) && $args['anchorPlacement'] !== 'top-bottom') {
458 $attributes['data-aos-anchor-placement'] = $args['anchorPlacement'];
459 }
460
461 return apply_filters('anfb_aos_attributes', $attributes, $args);
462 }
463
464
465 /**
466 * Add animation attributes to root element.
467 *
468 * @param string $html Block HTML.
469 * @param array $args Animation config.
470 * @return string Block HTML with animation attributes.
471 */
472 function add_animation_attributes($html, $args) {
473
474 $tags = new WP_HTML_Tag_Processor($html);
475 if($tags->next_tag()) {
476
477 /** Already has animation attributes. */
478 if($tags->get_attribute('data-aos')) {
479 return $html;
480 }
481
482 /** Add animation attributes. */
483 foreach(get_animation_attributes($args) as $key => $value) {
484 $tags->set_attribute($key, $value);
485 }
486
487 return $tags->get_updated_html();
488 }
489
490 return $html;
491 }
492
493
494 /**
495 * @param array $block_attributes
496 * @return bool Block attributes have an animation.
497 */
498 function has_animation($block_attributes) {
499 return (
500 is_array($block_attributes)
501 && isset($block_attributes['animationsForBlocks'])
502 && isset($block_attributes['animationsForBlocks']['animation'])
503 && !empty($block_attributes['animationsForBlocks']['animation'])
504 && $block_attributes['animationsForBlocks']['animation'] !== 'none'
505 );
506 }
507
508
509 /**
510 * @param array $block_attributes
511 * @return bool Block with these attributes is an animation provider.
512 */
513 function is_animation_provider($block_attributes) {
514 return (
515 is_array($block_attributes)
516 && isset($block_attributes['isAnimationProvider'])
517 && $block_attributes['isAnimationProvider']
518 );
519 }
520
521
522 /**
523 * @param array $block_context
524 * @return array|false
525 */
526 function get_animation_from_context($block_context) {
527
528 if(
529 is_array($block_context)
530 && isset($block_context['animationsForBlocksProvider'])
531 && $block_context['animationsForBlocksProvider']
532 && isset($block_context['animationsForBlocksAnimation'])
533 && is_array($block_context['animationsForBlocksAnimation'])
534 && isset($block_context['animationsForBlocksAnimation']['animation'])
535 && !empty($block_context['animationsForBlocksAnimation']['animation'])
536 && !in_array($block_context['animationsForBlocksAnimation']['animation'], ['none', 'inherit'], true)
537 ) {
538 if($block_context['animationsForBlocksAnimation']['animation'] === 'default') {
539 return get_default_animation();
540 }
541 return $block_context['animationsForBlocksAnimation'];
542 }
543
544 return false;
545 }
546
547
548 /**
549 * @return array Default animation configuration from plugin options.
550 */
551 function get_default_animation() {
552
553 static $default_animation = null;
554 if(!is_null($default_animation)) {
555 return $default_animation;
556 }
557
558 $options = get_option('animations-for-blocks');
559 if(isset($options['defaultAnimation'])) {
560 $default_animation = $options['defaultAnimation'];
561 return $default_animation;
562 }
563
564 $default_settings = get_default_settings();
565 $default_animation = $default_settings['defaultAnimation'];
566 return $default_animation;
567 }
568
569
570 /**
571 * @return bool This is likely the Block Editor rendering a dynamic block server-side.
572 */
573 function is_rest_edit_request() {
574 return (
575 defined('REST_REQUEST')
576 && REST_REQUEST
577 && isset($_REQUEST['context']) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
578 && $_REQUEST['context'] === 'edit' // phpcs:ignore WordPress.Security.NonceVerification.Recommended
579 );
580 }
581
582
583 /**
584 * Register blocks added by this plugin.
585 */
586 function register_blocks() {
587 register_block_type_from_metadata(__DIR__ . '/build/blocks/animation-container');
588 }
589 add_action('init', __NAMESPACE__ . '\\register_blocks');
590
591
592 /**
593 * @param array $args Array of arguments for registering a block type.
594 * @param string $block_name Block type name including namespace.
595 * @return array
596 */
597 function block_args($args, $block_name) {
598
599 if(!is_supported($block_name)) {
600 return $args;
601 }
602
603 if(!isset($args['attributes']) || !is_array($args['attributes'])) {
604 $args['attributes'] = [];
605 }
606
607 /** Register ANFB attribute, this is necessary for `/wp-json/wp/v2/block-renderer` REST endpoint to not throw `rest_additional_properties_forbidden`. */
608 $args['attributes']['animationsForBlocks'] = [
609 'type' => 'object',
610 ];
611
612 if(!isset($args['uses_context']) || !is_array($args['uses_context'])) {
613 $args['uses_context'] = [];
614 }
615
616 /** Ensure blocks that can use animations can also consume animation context values. */
617 foreach(get_animation_block_context_types() as $context_type) {
618 $args['uses_context'][] = $context_type;
619 }
620
621 return $args;
622 }
623 add_filter('register_block_type_args', __NAMESPACE__ . '\\block_args', 10, 2);
624
625
626 /**
627 * @see https://github.com/WordPress/gutenberg/issues/68608
628 * @param array $context Block context.
629 * @param array $parsed_block
630 * @param WP_Block|null $parent_block
631 * @return array
632 */
633 function filter_animation_context($context, $parsed_block, $parent_block) {
634
635 /** Used to retain animation-related block context values of the last `core/post-template` block that was rendered. */
636 static $stored_context = null;
637
638 /** Apply stored context to blocks that have the `inherit` animation inside Post Template block. */
639 if(
640 !is_null($stored_context)
641 && !is_null($parent_block)
642 && has_animation($parsed_block['attrs'])
643 && $parsed_block['attrs']['animationsForBlocks']['animation'] === 'inherit'
644 && isset($context['postType']) && isset($context['postId']) // Is probably in Post Template block
645 ) {
646 foreach($stored_context as $key => $value) {
647 $context[$key] = $value;
648 }
649 }
650
651 /** `core/post-template` doesn't pass the context down, store it manually. */
652 if(
653 $parsed_block['blockName'] === 'core/post-template'
654 && isset($context['animationsForBlocksProvider'])
655 && $context['animationsForBlocksProvider']
656 && isset($context['animationsForBlocksAnimation'])
657 ) {
658 $stored_context = [];
659 foreach(get_animation_block_context_types() as $context_type) {
660 if(isset($context[$context_type])) {
661 $stored_context[$context_type] = $context[$context_type];
662 }
663 }
664 }
665
666 return $context;
667 }
668 add_filter('render_block_context', __NAMESPACE__ . '\\filter_animation_context', 11, 3);
669
670
671 /**
672 * Add animation attributes to blocks' root HTML element when applicable.
673 *
674 * @param string $block_content Rendered block.
675 * @param string $parsed_block Parsed array representation of block.
676 * @param WP_Block $block
677 * @return string
678 */
679 function animate_block($block_content, $parsed_block, $block) {
680
681 static $cumulative_stagger = 0;
682 if($cumulative_stagger > 0 && is_animation_provider($parsed_block['attrs'])) {
683 $cumulative_stagger = 0;
684 }
685
686 if(
687 is_supported($parsed_block['blockName'])
688 && has_animation($parsed_block['attrs'] ?? [])
689 && !is_animation_provider($parsed_block['attrs'])
690 && !is_rest_edit_request() // Don't animate server-side rendered blocks.
691 ) {
692
693 /** Lazyload assets. */
694 static $lazyloaded = false;
695 if(!$lazyloaded) {
696
697 $options = get_option('animations-for-blocks');
698
699 if(isset($options['lenis']) && $options['lenis'] === 'animate') {
700 enqueue_front_end_lenis_assets();
701 }
702
703 if($options['lazyloadAssets']) {
704 enqueue_front_end_aos_assets();
705 }
706
707 $lazyloaded = true;
708 }
709
710 $animation_name = $parsed_block['attrs']['animationsForBlocks']['animation'];
711
712 /** Determine the animation configuration to use. */
713 if($animation_name === 'inherit') {
714 if($inherited_animation = get_animation_from_context($block->context)) {
715 $animation_config = $inherited_animation;
716 $stagger = (int)$block->context['animationsForBlocksStagger'];
717 $animation_config['delay'] = ($animation_config['delay'] ?? 0) + $cumulative_stagger;
718 $cumulative_stagger += $stagger;
719 } else {
720 return $block_content;
721 }
722 } elseif($animation_name === 'default') {
723 $animation_config = get_default_animation();
724 } else {
725 $animation_config = $parsed_block['attrs']['animationsForBlocks'];
726 }
727
728 return add_animation_attributes($block_content, $animation_config);
729 }
730
731 return $block_content;
732 }
733 add_filter('render_block', __NAMESPACE__ . '\\animate_block', 10, 3);
734
735
736 /**
737 * Add GitHub and Donate links on the plugins page.
738 *
739 * @param array $plugin_meta
740 * @param string $plugin_file
741 * @return array
742 */
743 function plugin_links($plugin_meta, $plugin_file) {
744 if($plugin_file === plugin_basename(WSD_ANFB_FILE)) {
745 $plugin_meta[] = '<a href="https://github.com/ska-dev-1/animations-for-blocks" target="_blank" rel="noopener noreferrer">GitHub</a>';
746 $plugin_meta[] = '<a href="https://buymeacoffee.com/skadev" target="_blank" rel="noopener noreferrer">Donate</a>';
747 }
748 return $plugin_meta;
749 }
750 add_filter('plugin_row_meta', __NAMESPACE__ . '\\plugin_links', 10, 2);
751