diff options
Diffstat (limited to 'plugins/jetpack/modules/lazy-images')
-rw-r--r-- | plugins/jetpack/modules/lazy-images/images/1x1.trans.gif | bin | 42 -> 0 bytes | |||
-rw-r--r-- | plugins/jetpack/modules/lazy-images/js/lazy-images.js | 870 | ||||
-rw-r--r-- | plugins/jetpack/modules/lazy-images/lazy-images.php | 353 |
3 files changed, 0 insertions, 1223 deletions
diff --git a/plugins/jetpack/modules/lazy-images/images/1x1.trans.gif b/plugins/jetpack/modules/lazy-images/images/1x1.trans.gif Binary files differdeleted file mode 100644 index f191b280..00000000 --- a/plugins/jetpack/modules/lazy-images/images/1x1.trans.gif +++ /dev/null diff --git a/plugins/jetpack/modules/lazy-images/js/lazy-images.js b/plugins/jetpack/modules/lazy-images/js/lazy-images.js deleted file mode 100644 index 31286bd3..00000000 --- a/plugins/jetpack/modules/lazy-images/js/lazy-images.js +++ /dev/null @@ -1,870 +0,0 @@ -/* globals IntersectionObserver, jQuery */ - -var jetpackLazyImagesModule = function( $ ) { - var images, - config = { - // If the image gets within 200px in the Y axis, start the download. - rootMargin: '200px 0px', - threshold: 0.01 - }, - imageCount = 0, - observer, - image, - i; - - $( document ).ready( function() { - lazy_load_init(); - - // Lazy load images that are brought in from Infinite Scroll - $( 'body' ).bind( 'post-load', lazy_load_init ); - - // Add event to provide optional compatibility for other code. - $( 'body' ).bind( 'jetpack-lazy-images-load', lazy_load_init ); - } ); - - function lazy_load_init() { - images = document.querySelectorAll( 'img.jetpack-lazy-image:not(.jetpack-lazy-image--handled)' ); - imageCount = images.length; - - // If initialized, then disconnect the observer - if ( observer ) { - observer.disconnect(); - } - - // If we don't have support for intersection observer, loads the images immediately - if ( ! ( 'IntersectionObserver' in window ) ) { - loadImagesImmediately( images ); - } else { - // It is supported, load the images - observer = new IntersectionObserver( onIntersection, config ); - - // foreach() is not supported in IE - for ( i = 0; i < images.length; i++ ) { - image = images[ i ]; - if ( image.getAttribute( 'data-lazy-loaded' ) ) { - continue; - } - - observer.observe( image ); - } - } - } - - /** - * Load all of the images immediately - * @param {NodeListOf<Element>} immediateImages List of lazy-loaded images to load immediately. - */ - function loadImagesImmediately( immediateImages ) { - var i; - - // foreach() is not supported in IE - for ( i = 0; i < immediateImages.length; i++ ) { - var image = immediateImages[ i ]; - applyImage( image ); - } - } - - /** - * On intersection - * @param {array} entries List of elements being observed. - */ - function onIntersection( entries ) { - var i; - - // Disconnect if we've already loaded all of the images - if ( imageCount === 0 ) { - observer.disconnect(); - } - - // Loop through the entries - for ( i = 0; i < entries.length; i++ ) { - var entry = entries[ i ]; - - // Are we in viewport? - if ( entry.intersectionRatio > 0 ) { - imageCount--; - - // Stop watching and load the image - observer.unobserve( entry.target ); - applyImage( entry.target ); - } - } - } - - /** - * Apply the image - * @param {object} image The image object. - */ - function applyImage( image ) { - var theImage = $( image ), - srcset, - sizes, - theClone; - - if ( ! theImage.length ) { - return; - } - - srcset = theImage.attr( 'data-lazy-srcset' ); - sizes = theImage.attr( 'data-lazy-sizes' ); - theClone = theImage.clone(); - - // Remove lazy attributes from the clone. - theClone.removeAttr( 'data-lazy-srcset' ), - theClone.removeAttr( 'data-lazy-sizes' ); - theClone.removeAttr( 'data-lazy-src' ); - - // Add the attributes we want on the finished image. - theClone.addClass( 'jetpack-lazy-image--handled' ); - theClone.attr( 'data-lazy-loaded', 1 ); - if ( ! srcset ) { - theClone.removeAttr( 'srcset' ); - } else { - theClone.attr( 'srcset', srcset ); - } - if ( sizes ) { - theClone.attr( 'sizes', sizes ); - } - - theImage.replaceWith( theClone ); - - // Fire an event so that third-party code can perform actions after an image is loaded. - theClone.trigger( 'jetpack-lazy-loaded-image' ); - } -}; - -/** - * The following is an Intersection observer polyfill which is licensed under - * the W3C SOFTWARE AND DOCUMENT NOTICE AND LICENSE and can be found at: - * https://github.com/w3c/IntersectionObserver/tree/master/polyfill - */ - -/* jshint ignore:start */ -/** - * Copyright 2016 Google Inc. All Rights Reserved. - * - * Licensed under the W3C SOFTWARE AND DOCUMENT NOTICE AND LICENSE. - * - * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document - * - */ - -(function(window, document) { - 'use strict'; - - - // Exits early if all IntersectionObserver and IntersectionObserverEntry - // features are natively supported. - if ('IntersectionObserver' in window && - 'IntersectionObserverEntry' in window && - 'intersectionRatio' in window.IntersectionObserverEntry.prototype) { - - // Minimal polyfill for Edge 15's lack of `isIntersecting` - // See: https://github.com/w3c/IntersectionObserver/issues/211 - if (!('isIntersecting' in window.IntersectionObserverEntry.prototype)) { - Object.defineProperty(window.IntersectionObserverEntry.prototype, - 'isIntersecting', { - get: function () { - return this.intersectionRatio > 0; - } - }); - } - return; - } - - - /** - * An IntersectionObserver registry. This registry exists to hold a strong - * reference to IntersectionObserver instances currently observering a target - * element. Without this registry, instances without another reference may be - * garbage collected. - */ - var registry = []; - - - /** - * Creates the global IntersectionObserverEntry constructor. - * https://w3c.github.io/IntersectionObserver/#intersection-observer-entry - * @param {Object} entry A dictionary of instance properties. - * @constructor - */ - function IntersectionObserverEntry(entry) { - this.time = entry.time; - this.target = entry.target; - this.rootBounds = entry.rootBounds; - this.boundingClientRect = entry.boundingClientRect; - this.intersectionRect = entry.intersectionRect || getEmptyRect(); - this.isIntersecting = !!entry.intersectionRect; - - // Calculates the intersection ratio. - var targetRect = this.boundingClientRect; - var targetArea = targetRect.width * targetRect.height; - var intersectionRect = this.intersectionRect; - var intersectionArea = intersectionRect.width * intersectionRect.height; - - // Sets intersection ratio. - if (targetArea) { - this.intersectionRatio = intersectionArea / targetArea; - } else { - // If area is zero and is intersecting, sets to 1, otherwise to 0 - this.intersectionRatio = this.isIntersecting ? 1 : 0; - } - } - - - /** - * Creates the global IntersectionObserver constructor. - * https://w3c.github.io/IntersectionObserver/#intersection-observer-interface - * @param {Function} callback The function to be invoked after intersection - * changes have queued. The function is not invoked if the queue has - * been emptied by calling the `takeRecords` method. - * @param {Object=} opt_options Optional configuration options. - * @constructor - */ - function IntersectionObserver(callback, opt_options) { - - var options = opt_options || {}; - - if (typeof callback != 'function') { - throw new Error('callback must be a function'); - } - - if (options.root && options.root.nodeType != 1) { - throw new Error('root must be an Element'); - } - - // Binds and throttles `this._checkForIntersections`. - this._checkForIntersections = throttle( - this._checkForIntersections.bind(this), this.THROTTLE_TIMEOUT); - - // Private properties. - this._callback = callback; - this._observationTargets = []; - this._queuedEntries = []; - this._rootMarginValues = this._parseRootMargin(options.rootMargin); - - // Public properties. - this.thresholds = this._initThresholds(options.threshold); - this.root = options.root || null; - this.rootMargin = this._rootMarginValues.map(function(margin) { - return margin.value + margin.unit; - }).join(' '); - } - - - /** - * The minimum interval within which the document will be checked for - * intersection changes. - */ - IntersectionObserver.prototype.THROTTLE_TIMEOUT = 100; - - - /** - * The frequency in which the polyfill polls for intersection changes. - * this can be updated on a per instance basis and must be set prior to - * calling `observe` on the first target. - */ - IntersectionObserver.prototype.POLL_INTERVAL = null; - - /** - * Use a mutation observer on the root element - * to detect intersection changes. - */ - IntersectionObserver.prototype.USE_MUTATION_OBSERVER = true; - - - /** - * Starts observing a target element for intersection changes based on - * the thresholds values. - * @param {Element} target The DOM element to observe. - */ - IntersectionObserver.prototype.observe = function(target) { - var isTargetAlreadyObserved = this._observationTargets.some(function(item) { - return item.element == target; - }); - - if (isTargetAlreadyObserved) { - return; - } - - if (!(target && target.nodeType == 1)) { - throw new Error('target must be an Element'); - } - - this._registerInstance(); - this._observationTargets.push({element: target, entry: null}); - this._monitorIntersections(); - this._checkForIntersections(); - }; - - - /** - * Stops observing a target element for intersection changes. - * @param {Element} target The DOM element to observe. - */ - IntersectionObserver.prototype.unobserve = function(target) { - this._observationTargets = - this._observationTargets.filter(function(item) { - - return item.element != target; - }); - if (!this._observationTargets.length) { - this._unmonitorIntersections(); - this._unregisterInstance(); - } - }; - - - /** - * Stops observing all target elements for intersection changes. - */ - IntersectionObserver.prototype.disconnect = function() { - this._observationTargets = []; - this._unmonitorIntersections(); - this._unregisterInstance(); - }; - - - /** - * Returns any queue entries that have not yet been reported to the - * callback and clears the queue. This can be used in conjunction with the - * callback to obtain the absolute most up-to-date intersection information. - * @return {Array} The currently queued entries. - */ - IntersectionObserver.prototype.takeRecords = function() { - var records = this._queuedEntries.slice(); - this._queuedEntries = []; - return records; - }; - - - /** - * Accepts the threshold value from the user configuration object and - * returns a sorted array of unique threshold values. If a value is not - * between 0 and 1 and error is thrown. - * @private - * @param {Array|number=} opt_threshold An optional threshold value or - * a list of threshold values, defaulting to [0]. - * @return {Array} A sorted list of unique and valid threshold values. - */ - IntersectionObserver.prototype._initThresholds = function(opt_threshold) { - var threshold = opt_threshold || [0]; - if (!Array.isArray(threshold)) threshold = [threshold]; - - return threshold.sort().filter(function(t, i, a) { - if (typeof t != 'number' || isNaN(t) || t < 0 || t > 1) { - throw new Error('threshold must be a number between 0 and 1 inclusively'); - } - return t !== a[i - 1]; - }); - }; - - - /** - * Accepts the rootMargin value from the user configuration object - * and returns an array of the four margin values as an object containing - * the value and unit properties. If any of the values are not properly - * formatted or use a unit other than px or %, and error is thrown. - * @private - * @param {string=} opt_rootMargin An optional rootMargin value, - * defaulting to '0px'. - * @return {Array<Object>} An array of margin objects with the keys - * value and unit. - */ - IntersectionObserver.prototype._parseRootMargin = function(opt_rootMargin) { - var marginString = opt_rootMargin || '0px'; - var margins = marginString.split(/\s+/).map(function(margin) { - var parts = /^(-?\d*\.?\d+)(px|%)$/.exec(margin); - if (!parts) { - throw new Error('rootMargin must be specified in pixels or percent'); - } - return {value: parseFloat(parts[1]), unit: parts[2]}; - }); - - // Handles shorthand. - margins[1] = margins[1] || margins[0]; - margins[2] = margins[2] || margins[0]; - margins[3] = margins[3] || margins[1]; - - return margins; - }; - - - /** - * Starts polling for intersection changes if the polling is not already - * happening, and if the page's visibilty state is visible. - * @private - */ - IntersectionObserver.prototype._monitorIntersections = function() { - if (!this._monitoringIntersections) { - this._monitoringIntersections = true; - - // If a poll interval is set, use polling instead of listening to - // resize and scroll events or DOM mutations. - if (this.POLL_INTERVAL) { - this._monitoringInterval = setInterval( - this._checkForIntersections, this.POLL_INTERVAL); - } - else { - addEvent(window, 'resize', this._checkForIntersections, true); - addEvent(document, 'scroll', this._checkForIntersections, true); - - if (this.USE_MUTATION_OBSERVER && 'MutationObserver' in window) { - this._domObserver = new MutationObserver(this._checkForIntersections); - this._domObserver.observe(document, { - attributes: true, - childList: true, - characterData: true, - subtree: true - }); - } - } - } - }; - - - /** - * Stops polling for intersection changes. - * @private - */ - IntersectionObserver.prototype._unmonitorIntersections = function() { - if (this._monitoringIntersections) { - this._monitoringIntersections = false; - - clearInterval(this._monitoringInterval); - this._monitoringInterval = null; - - removeEvent(window, 'resize', this._checkForIntersections, true); - removeEvent(document, 'scroll', this._checkForIntersections, true); - - if (this._domObserver) { - this._domObserver.disconnect(); - this._domObserver = null; - } - } - }; - - - /** - * Scans each observation target for intersection changes and adds them - * to the internal entries queue. If new entries are found, it - * schedules the callback to be invoked. - * @private - */ - IntersectionObserver.prototype._checkForIntersections = function() { - var rootIsInDom = this._rootIsInDom(); - var rootRect = rootIsInDom ? this._getRootRect() : getEmptyRect(); - - this._observationTargets.forEach(function(item) { - var target = item.element; - var targetRect = getBoundingClientRect(target); - var rootContainsTarget = this._rootContainsTarget(target); - var oldEntry = item.entry; - var intersectionRect = rootIsInDom && rootContainsTarget && - this._computeTargetAndRootIntersection(target, rootRect); - - var newEntry = item.entry = new IntersectionObserverEntry({ - time: now(), - target: target, - boundingClientRect: targetRect, - rootBounds: rootRect, - intersectionRect: intersectionRect - }); - - if (!oldEntry) { - this._queuedEntries.push(newEntry); - } else if (rootIsInDom && rootContainsTarget) { - // If the new entry intersection ratio has crossed any of the - // thresholds, add a new entry. - if (this._hasCrossedThreshold(oldEntry, newEntry)) { - this._queuedEntries.push(newEntry); - } - } else { - // If the root is not in the DOM or target is not contained within - // root but the previous entry for this target had an intersection, - // add a new record indicating removal. - if (oldEntry && oldEntry.isIntersecting) { - this._queuedEntries.push(newEntry); - } - } - }, this); - - if (this._queuedEntries.length) { - this._callback(this.takeRecords(), this); - } - }; - - - /** - * Accepts a target and root rect computes the intersection between then - * following the algorithm in the spec. - * TODO(philipwalton): at this time clip-path is not considered. - * https://w3c.github.io/IntersectionObserver/#calculate-intersection-rect-algo - * @param {Element} target The target DOM element - * @param {Object} rootRect The bounding rect of the root after being - * expanded by the rootMargin value. - * @return {?Object} The final intersection rect object or undefined if no - * intersection is found. - * @private - */ - IntersectionObserver.prototype._computeTargetAndRootIntersection = - function(target, rootRect) { - - // If the element isn't displayed, an intersection can't happen. - if (window.getComputedStyle(target).display == 'none') return; - - var targetRect = getBoundingClientRect(target); - var intersectionRect = targetRect; - var parent = getParentNode(target); - var atRoot = false; - - while (!atRoot) { - var parentRect = null; - var parentComputedStyle = parent.nodeType == 1 ? - window.getComputedStyle(parent) : {}; - - // If the parent isn't displayed, an intersection can't happen. - if (parentComputedStyle.display == 'none') return; - - if (parent == this.root || parent == document) { - atRoot = true; - parentRect = rootRect; - } else { - // If the element has a non-visible overflow, and it's not the <body> - // or <html> element, update the intersection rect. - // Note: <body> and <html> cannot be clipped to a rect that's not also - // the document rect, so no need to compute a new intersection. - if (parent != document.body && - parent != document.documentElement && - parentComputedStyle.overflow != 'visible') { - parentRect = getBoundingClientRect(parent); - } - } - - // If either of the above conditionals set a new parentRect, - // calculate new intersection data. - if (parentRect) { - intersectionRect = computeRectIntersection(parentRect, intersectionRect); - - if (!intersectionRect) break; - } - parent = getParentNode(parent); - } - return intersectionRect; - }; - - - /** - * Returns the root rect after being expanded by the rootMargin value. - * @return {Object} The expanded root rect. - * @private - */ - IntersectionObserver.prototype._getRootRect = function() { - var rootRect; - if (this.root) { - rootRect = getBoundingClientRect(this.root); - } else { - // Use <html>/<body> instead of window since scroll bars affect size. - var html = document.documentElement; - var body = document.body; - rootRect = { - top: 0, - left: 0, - right: html.clientWidth || body.clientWidth, - width: html.clientWidth || body.clientWidth, - bottom: html.clientHeight || body.clientHeight, - height: html.clientHeight || body.clientHeight - }; - } - return this._expandRectByRootMargin(rootRect); - }; - - - /** - * Accepts a rect and expands it by the rootMargin value. - * @param {Object} rect The rect object to expand. - * @return {Object} The expanded rect. - * @private - */ - IntersectionObserver.prototype._expandRectByRootMargin = function(rect) { - var margins = this._rootMarginValues.map(function(margin, i) { - return margin.unit == 'px' ? margin.value : - margin.value * (i % 2 ? rect.width : rect.height) / 100; - }); - var newRect = { - top: rect.top - margins[0], - right: rect.right + margins[1], - bottom: rect.bottom + margins[2], - left: rect.left - margins[3] - }; - newRect.width = newRect.right - newRect.left; - newRect.height = newRect.bottom - newRect.top; - - return newRect; - }; - - - /** - * Accepts an old and new entry and returns true if at least one of the - * threshold values has been crossed. - * @param {?IntersectionObserverEntry} oldEntry The previous entry for a - * particular target element or null if no previous entry exists. - * @param {IntersectionObserverEntry} newEntry The current entry for a - * particular target element. - * @return {boolean} Returns true if a any threshold has been crossed. - * @private - */ - IntersectionObserver.prototype._hasCrossedThreshold = - function(oldEntry, newEntry) { - - // To make comparing easier, an entry that has a ratio of 0 - // but does not actually intersect is given a value of -1 - var oldRatio = oldEntry && oldEntry.isIntersecting ? - oldEntry.intersectionRatio || 0 : -1; - var newRatio = newEntry.isIntersecting ? - newEntry.intersectionRatio || 0 : -1; - - // Ignore unchanged ratios - if (oldRatio === newRatio) return; - - for (var i = 0; i < this.thresholds.length; i++) { - var threshold = this.thresholds[i]; - - // Return true if an entry matches a threshold or if the new ratio - // and the old ratio are on the opposite sides of a threshold. - if (threshold == oldRatio || threshold == newRatio || - threshold < oldRatio !== threshold < newRatio) { - return true; - } - } - }; - - - /** - * Returns whether or not the root element is an element and is in the DOM. - * @return {boolean} True if the root element is an element and is in the DOM. - * @private - */ - IntersectionObserver.prototype._rootIsInDom = function() { - return !this.root || containsDeep(document, this.root); - }; - - - /** - * Returns whether or not the target element is a child of root. - * @param {Element} target The target element to check. - * @return {boolean} True if the target element is a child of root. - * @private - */ - IntersectionObserver.prototype._rootContainsTarget = function(target) { - return containsDeep(this.root || document, target); - }; - - - /** - * Adds the instance to the global IntersectionObserver registry if it isn't - * already present. - * @private - */ - IntersectionObserver.prototype._registerInstance = function() { - if (registry.indexOf(this) < 0) { - registry.push(this); - } - }; - - - /** - * Removes the instance from the global IntersectionObserver registry. - * @private - */ - IntersectionObserver.prototype._unregisterInstance = function() { - var index = registry.indexOf(this); - if (index != -1) registry.splice(index, 1); - }; - - - /** - * Returns the result of the performance.now() method or null in browsers - * that don't support the API. - * @return {number} The elapsed time since the page was requested. - */ - function now() { - return window.performance && performance.now && performance.now(); - } - - - /** - * Throttles a function and delays its executiong, so it's only called at most - * once within a given time period. - * @param {Function} fn The function to throttle. - * @param {number} timeout The amount of time that must pass before the - * function can be called again. - * @return {Function} The throttled function. - */ - function throttle(fn, timeout) { - var timer = null; - return function () { - if (!timer) { - timer = setTimeout(function() { - fn(); - timer = null; - }, timeout); - } - }; - } - - - /** - * Adds an event handler to a DOM node ensuring cross-browser compatibility. - * @param {Node} node The DOM node to add the event handler to. - * @param {string} event The event name. - * @param {Function} fn The event handler to add. - * @param {boolean} opt_useCapture Optionally adds the even to the capture - * phase. Note: this only works in modern browsers. - */ - function addEvent(node, event, fn, opt_useCapture) { - if (typeof node.addEventListener == 'function') { - node.addEventListener(event, fn, opt_useCapture || false); - } - else if (typeof node.attachEvent == 'function') { - node.attachEvent('on' + event, fn); - } - } - - - /** - * Removes a previously added event handler from a DOM node. - * @param {Node} node The DOM node to remove the event handler from. - * @param {string} event The event name. - * @param {Function} fn The event handler to remove. - * @param {boolean} opt_useCapture If the event handler was added with this - * flag set to true, it should be set to true here in order to remove it. - */ - function removeEvent(node, event, fn, opt_useCapture) { - if (typeof node.removeEventListener == 'function') { - node.removeEventListener(event, fn, opt_useCapture || false); - } - else if (typeof node.detatchEvent == 'function') { - node.detatchEvent('on' + event, fn); - } - } - - - /** - * Returns the intersection between two rect objects. - * @param {Object} rect1 The first rect. - * @param {Object} rect2 The second rect. - * @return {?Object} The intersection rect or undefined if no intersection - * is found. - */ - function computeRectIntersection(rect1, rect2) { - var top = Math.max(rect1.top, rect2.top); - var bottom = Math.min(rect1.bottom, rect2.bottom); - var left = Math.max(rect1.left, rect2.left); - var right = Math.min(rect1.right, rect2.right); - var width = right - left; - var height = bottom - top; - - return (width >= 0 && height >= 0) && { - top: top, - bottom: bottom, - left: left, - right: right, - width: width, - height: height - }; - } - - - /** - * Shims the native getBoundingClientRect for compatibility with older IE. - * @param {Element} el The element whose bounding rect to get. - * @return {Object} The (possibly shimmed) rect of the element. - */ - function getBoundingClientRect(el) { - var rect; - - try { - rect = el.getBoundingClientRect(); - } catch (err) { - // Ignore Windows 7 IE11 "Unspecified error" - // https://github.com/w3c/IntersectionObserver/pull/205 - } - - if (!rect) return getEmptyRect(); - - // Older IE - if (!(rect.width && rect.height)) { - rect = { - top: rect.top, - right: rect.right, - bottom: rect.bottom, - left: rect.left, - width: rect.right - rect.left, - height: rect.bottom - rect.top - }; - } - return rect; - } - - - /** - * Returns an empty rect object. An empty rect is returned when an element - * is not in the DOM. - * @return {Object} The empty rect. - */ - function getEmptyRect() { - return { - top: 0, - bottom: 0, - left: 0, - right: 0, - width: 0, - height: 0 - }; - } - - /** - * Checks to see if a parent element contains a child elemnt (including inside - * shadow DOM). - * @param {Node} parent The parent element. - * @param {Node} child The child element. - * @return {boolean} True if the parent node contains the child node. - */ - function containsDeep(parent, child) { - var node = child; - while (node) { - if (node == parent) return true; - - node = getParentNode(node); - } - return false; - } - - - /** - * Gets the parent node of an element or its host element if the parent node - * is a shadow root. - * @param {Node} node The node whose parent to get. - * @return {Node|null} The parent node or null if no parent exists. - */ - function getParentNode(node) { - var parent = node.parentNode; - - if (parent && parent.nodeType == 11 && parent.host) { - // If the parent is a shadow root, return the host element. - return parent.host; - } - return parent; - } - - - // Exposes the constructors globally. - window.IntersectionObserver = IntersectionObserver; - window.IntersectionObserverEntry = IntersectionObserverEntry; - - }(window, document)); -/* jshint ignore:end */ - -// Let's kick things off now -jetpackLazyImagesModule( jQuery ); diff --git a/plugins/jetpack/modules/lazy-images/lazy-images.php b/plugins/jetpack/modules/lazy-images/lazy-images.php deleted file mode 100644 index befa5ec8..00000000 --- a/plugins/jetpack/modules/lazy-images/lazy-images.php +++ /dev/null @@ -1,353 +0,0 @@ -<?php - -class Jetpack_Lazy_Images { - private static $__instance = null; - - /** - * Singleton implementation - * - * @return object - */ - public static function instance() { - if ( is_null( self::$__instance ) ) { - self::$__instance = new Jetpack_Lazy_Images(); - } - - return self::$__instance; - } - - /** - * Registers actions - */ - private function __construct() { - if ( is_admin() ) { - return; - } - - /** - * Whether the lazy-images module should load. - * - * This filter is not prefixed with jetpack_ to provide a smoother migration - * process from the WordPress Lazy Load plugin. - * - * @module lazy-images - * - * @since 5.6.0 - * - * @param bool true Whether lazy image loading should occur. - */ - if ( ! apply_filters( 'lazyload_is_enabled', true ) ) { - return; - } - - if ( Jetpack_AMP_Support::is_amp_request() ) { - return; - } - - add_action( 'wp_head', array( $this, 'setup_filters' ), 9999 ); // we don't really want to modify anything in <head> since it's mostly all metadata - add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) ); - - // Do not lazy load avatar in admin bar - add_action( 'admin_bar_menu', array( $this, 'remove_filters' ), 0 ); - - add_filter( 'wp_kses_allowed_html', array( $this, 'allow_lazy_attributes' ) ); - add_action( 'wp_head', array( $this, 'add_nojs_fallback' ) ); - } - - public function setup_filters() { - add_filter( 'the_content', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); // run this later, so other content filters have run, including image_add_wh on WP.com - add_filter( 'post_thumbnail_html', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); - add_filter( 'get_avatar', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); - add_filter( 'widget_text', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); - add_filter( 'get_image_tag', array( $this, 'add_image_placeholders' ), PHP_INT_MAX); - add_filter( 'wp_get_attachment_image_attributes', array( __CLASS__, 'process_image_attributes' ), PHP_INT_MAX ); - } - - public function remove_filters() { - remove_filter( 'the_content', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); - remove_filter( 'post_thumbnail_html', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); - remove_filter( 'get_avatar', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); - remove_filter( 'widget_text', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); - remove_filter( 'get_image_tag', array( $this, 'add_image_placeholders' ), PHP_INT_MAX); - remove_filter( 'wp_get_attachment_image_attributes', array( __CLASS__, 'process_image_attributes' ), PHP_INT_MAX ); - } - - /** - * Ensure that our lazy image attributes are not filtered out of image tags. - * - * @param array $allowed_tags The allowed tags and their attributes. - * @return array - */ - public function allow_lazy_attributes( $allowed_tags ) { - if ( ! isset( $allowed_tags['img'] ) ) { - return $allowed_tags; - } - - // But, if images are allowed, ensure that our attributes are allowed! - $img_attributes = array_merge( $allowed_tags['img'], array( - 'data-lazy-src' => 1, - 'data-lazy-srcset' => 1, - 'data-lazy-sizes' => 1, - ) ); - - $allowed_tags['img'] = $img_attributes; - - return $allowed_tags; - } - - public function add_image_placeholders( $content ) { - // Don't lazyload for feeds, previews - if ( is_feed() || is_preview() ) { - return $content; - } - - // Don't lazy-load if the content has already been run through previously - if ( false !== strpos( $content, 'data-lazy-src' ) ) { - return $content; - } - - // This is a pretty simple regex, but it works - $content = preg_replace_callback( '#<(img)([^>]+?)(>(.*?)</\\1>|[\/]?>)#si', array( __CLASS__, 'process_image' ), $content ); - - return $content; - } - - /** - * Returns true when a given string of classes contains a class signifying lazy images - * should not process the image. - * - * @since 5.9.0 - * - * @param string $classes A string of space-separated classes. - * @return bool - */ - public static function should_skip_image_with_blacklisted_class( $classes ) { - $blacklisted_classes = array( - 'skip-lazy', - 'gazette-featured-content-thumbnail', - ); - - /** - * Allow plugins and themes to tell lazy images to skip an image with a given class. - * - * @module lazy-images - * - * @since 5.9.0 - * - * @param array An array of strings where each string is a class. - */ - $blacklisted_classes = apply_filters( 'jetpack_lazy_images_blacklisted_classes', $blacklisted_classes ); - - if ( ! is_array( $blacklisted_classes ) || empty( $blacklisted_classes ) ) { - return false; - } - - foreach ( $blacklisted_classes as $class ) { - if ( false !== strpos( $classes, $class ) ) { - return true; - } - } - - return false; - } - - /** - * Processes images in content by acting as the preg_replace_callback - * - * @since 5.6.0 - * - * @param array $matches - * - * @return string The image with updated lazy attributes - */ - static function process_image( $matches ) { - $old_attributes_str = $matches[2]; - $old_attributes_kses_hair = wp_kses_hair( $old_attributes_str, wp_allowed_protocols() ); - - if ( empty( $old_attributes_kses_hair['src'] ) ) { - return $matches[0]; - } - - $old_attributes = self::flatten_kses_hair_data( $old_attributes_kses_hair ); - - // If we didn't add lazy attributes, just return the original image source. - if ( ! empty( $old_attributes['class'] ) && false !== strpos( $old_attributes['class'], 'jetpack-lazy-image' ) ) { - return $matches[0]; - } - - $new_attributes = self::process_image_attributes( $old_attributes ); - $new_attributes_str = self::build_attributes_string( $new_attributes ); - - return sprintf( '<img %1$s><noscript>%2$s</noscript>', $new_attributes_str, $matches[0] ); - } - - /** - * Given an array of image attributes, updates the `src`, `srcset`, and `sizes` attributes so - * that they load lazily. - * - * @since 5.7.0 - * - * @param array $attributes - * - * @return array The updated image attributes array with lazy load attributes - */ - static function process_image_attributes( $attributes ) { - if ( empty( $attributes['src'] ) ) { - return $attributes; - } - - if ( ! empty( $attributes['class'] ) && self::should_skip_image_with_blacklisted_class( $attributes['class'] ) ) { - return $attributes; - } - - /** - * Allow plugins and themes to conditionally skip processing an image via its attributes. - * - * @module-lazy-images - * - * @deprecated 6.5.0 Use jetpack_lazy_images_skip_image_with_attributes instead. - * - * @since 5.9.0 - * - * @param bool Default to not skip processing the current image. - * @param array An array of attributes via wp_kses_hair() for the current image. - */ - if ( apply_filters( 'jetpack_lazy_images_skip_image_with_atttributes', false, $attributes ) ) { - return $attributes; - } - - /** - * Allow plugins and themes to conditionally skip processing an image via its attributes. - * - * @module-lazy-images - * - * @since 6.5.0 Filter name was updated from jetpack_lazy_images_skip_image_with_atttributes to correct typo. - * @since 5.9.0 - * - * @param bool Default to not skip processing the current image. - * @param array An array of attributes via wp_kses_hair() for the current image. - */ - if ( apply_filters( 'jetpack_lazy_images_skip_image_with_attributes', false, $attributes ) ) { - return $attributes; - } - - $old_attributes = $attributes; - - // Stash srcset and sizes in data attributes. - foreach ( array( 'srcset', 'sizes' ) as $attribute ) { - if ( isset( $old_attributes[ $attribute ] ) ) { - $attributes[ "data-lazy-$attribute" ] = $old_attributes[ $attribute ]; - unset( $attributes[ $attribute ] ); - } - } - - // We set this, adding the query arg so that it doesn't exactly equal the src attribute, so that photon JavaScript - // will hold off on processing this image. - $attributes['data-lazy-src'] = esc_url_raw( add_query_arg( 'is-pending-load', true, $attributes['src'] ) ); - - $attributes['srcset'] = self::get_placeholder_image(); - $attributes['class'] = sprintf( - '%s jetpack-lazy-image', - empty( $old_attributes['class'] ) - ? '' - : $old_attributes['class'] - ); - - /** - * Allow plugins and themes to override the attributes on the image before the content is updated. - * - * One potential use of this filter is for themes that set `height:auto` on the `img` tag. - * With this filter, the theme could get the width and height attributes from the - * $attributes array and then add a style tag that sets those values as well, which could - * minimize reflow as images load. - * - * @module lazy-images - * - * @since 5.6.0 - * - * @param array An array containing the attributes for the image, where the key is the attribute name - * and the value is the attribute value. - */ - return apply_filters( 'jetpack_lazy_images_new_attributes', $attributes ); - } - - /** - * Adds JavaScript to check if the current browser supports JavaScript as well as some styles to hide lazy - * images when the browser does not support JavaScript. - * - * @return void - */ - public function add_nojs_fallback() { - ?> - <style type="text/css"> - /* If html does not have either class, do not show lazy loaded images. */ - html:not( .jetpack-lazy-images-js-enabled ):not( .js ) .jetpack-lazy-image { - display: none; - } - </style> - <script> - document.documentElement.classList.add( - 'jetpack-lazy-images-js-enabled' - ); - </script> - <?php - } - - /** - * Retrieves the placeholder image after running it through the lazyload_images_placeholder_image filter. - * - * @return string The placeholder image source. - */ - private static function get_placeholder_image() { - /** - * Allows plugins and themes to modify the placeholder image. - * - * This filter is not prefixed with jetpack_ to provide a smoother migration - * process from the WordPress Lazy Load plugin. - * - * @module lazy-images - * - * @since 5.6.0 - * @since 6.5.0 Default image is now a base64 encoded transparent gif. - * - * @param string The URL to the placeholder image - */ - return apply_filters( - 'lazyload_images_placeholder_image', - 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' - ); - } - - private static function flatten_kses_hair_data( $attributes ) { - $flattened_attributes = array(); - foreach ( $attributes as $name => $attribute ) { - $flattened_attributes[ $name ] = $attribute['value']; - } - return $flattened_attributes; - } - - private static function build_attributes_string( $attributes ) { - $string = array(); - foreach ( $attributes as $name => $value ) { - if ( '' === $value ) { - $string[] = sprintf( '%s', $name ); - } else { - $string[] = sprintf( '%s="%s"', $name, esc_attr( $value ) ); - } - } - return implode( ' ', $string ); - } - - public function enqueue_assets() { - wp_enqueue_script( - 'jetpack-lazy-images', - Jetpack::get_file_url_for_environment( - '_inc/build/lazy-images/js/lazy-images.min.js', - 'modules/lazy-images/js/lazy-images.js' - ), - array( 'jquery' ), - JETPACK__VERSION, - true - ); - } -} |