Make WordPress Core

Changeset 56541


Ignore:
Timestamp:
09/07/2023 08:35:38 PM (3 years ago)
Author:
joedolson
Message:

Plugins: Respect prefers-reduced-motion on plugin thumbnails.

Pause animated plugin thumbnails when a user has reduced motion preferences configured in their device or operating system.

Props Travel_girl, audrasjb, bordoni, stevejonesdev, joedolson.
Fixes #55723.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/js/_enqueues/admin/common.js

    r56243 r56541  
    21052105
    21062106}( jQuery, window ));
     2107
     2108/**
     2109 * Freeze animated plugin icons when reduced motion is enabled.
     2110 *
     2111 * When the user has enabled the 'prefers-reduced-motion' setting, this module
     2112 * stops animations for all GIFs on the page with the class 'plugin-icon' or
     2113 * plugin icon images in the update plugins table.
     2114 *
     2115 * @since 6.4
     2116 */
     2117(function() {
     2118        // Private variables and methods.
     2119        var priv = {},
     2120                pub = {},
     2121                mediaQuery;
     2122
     2123        // Initialize pauseAll to false; it will be set to true if reduced motion is preferred.
     2124        priv.pauseAll = false;
     2125        if ( window.matchMedia ) {
     2126                mediaQuery = window.matchMedia( '(prefers-reduced-motion: reduce)' );
     2127                if ( ! mediaQuery || mediaQuery.matches ) {
     2128                        priv.pauseAll = true;
     2129                }
     2130        }
     2131
     2132        // Method to replace animated GIFs with a static frame.
     2133        priv.freezeAnimatedPluginIcons = function( img ) {
     2134                var coverImage = function() {
     2135                        var width = img.width;
     2136                        var height = img.height;
     2137                        var canvas = document.createElement( 'canvas' );
     2138                       
     2139                        // Set canvas dimensions.
     2140                        canvas.width = width;
     2141                        canvas.height = height;
     2142
     2143                        // Copy classes from the image to the canvas.
     2144                        canvas.className = img.className;
     2145
     2146                        // Check if the image is inside a specific table.
     2147                        var isInsideUpdateTable = img.closest( '#update-plugins-table' );
     2148
     2149                        if ( isInsideUpdateTable ) {
     2150                                // Transfer computed styles from image to canvas.
     2151                                var computedStyles = window.getComputedStyle( img ),
     2152                                        i, max;
     2153                                for ( i = 0, max = computedStyles.length; i < max; i++ ) {
     2154                                        var propName = computedStyles[ i ];
     2155                                        var propValue = computedStyles.getPropertyValue( propName );
     2156                                        canvas.style[ propName ] = propValue;
     2157                                }
     2158                        }
     2159
     2160                        // Draw the image onto the canvas.
     2161                        canvas.getContext( '2d' ).drawImage( img, 0, 0, width, height );
     2162
     2163                        // Set accessibility attributes on canvas.
     2164                        canvas.setAttribute( 'aria-hidden', 'true' );
     2165                        canvas.setAttribute( 'role', 'presentation' );
     2166
     2167                        // Insert canvas before the image and set the image to be near-invisible.
     2168                        var parent = img.parentNode;
     2169                        parent.insertBefore( canvas, img );
     2170                        img.style.opacity = 0.01;
     2171                        img.style.width = '0px';
     2172                        img.style.height = '0px';
     2173                };
     2174
     2175                // If the image is already loaded, apply the coverImage function.
     2176                if ( img.complete ) {
     2177                        coverImage();
     2178                } else {
     2179                        // Otherwise, wait for the image to load.
     2180                        img.addEventListener( 'load', coverImage, true );
     2181                }
     2182        };
     2183
     2184        // Public method to freeze all relevant GIFs on the page.
     2185        pub.freezeAll = function() {
     2186                var images = document.querySelectorAll( '.plugin-icon, #update-plugins-table img' );
     2187                for ( var x = 0; x < images.length; x++ ) {
     2188                        if ( /\.gif(?:\?|$)/i.test( images[ x ].src ) ) {
     2189                                priv.freezeAnimatedPluginIcons( images[ x ] );
     2190                        }
     2191                }
     2192        };
     2193
     2194        // Only run the freezeAll method if the user prefers reduced motion.
     2195        if ( true === priv.pauseAll ) {
     2196                pub.freezeAll();
     2197        }
     2198
     2199        // Listen for jQuery AJAX events.
     2200        ( function( $ ) {
     2201                $( document ).ajaxComplete( function( event, xhr, settings ) {
     2202                        // Check if this is the 'search-install-plugins' request.
     2203                        if ( settings.data && settings.data.includes( 'action=search-install-plugins' ) ) {
     2204                                // Recheck if the user prefers reduced motion.
     2205                                if ( window.matchMedia ) {
     2206                                        var mediaQuery = window.matchMedia( '(prefers-reduced-motion: reduce)' );
     2207                                        if ( mediaQuery.matches ) {
     2208                                                pub.freezeAll();
     2209                                        }
     2210                                } else {
     2211                                        // Fallback for browsers that don't support matchMedia.
     2212                                        if ( true === priv.pauseAll ) {
     2213                                                pub.freezeAll();
     2214                                        }
     2215                                }
     2216                        }
     2217                } );
     2218        } )( jQuery );
     2219
     2220        // Expose public methods.
     2221        return pub;
     2222})();
Note: See TracChangeset for help on using the changeset viewer.