Make WordPress Core

Changeset 56541


Ignore:
Timestamp:
09/07/2023 08:35:38 PM (9 months 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.