├── registration.php
├── view
├── frontend
│ ├── templates
│ │ └── preload.phtml
│ ├── layout
│ │ ├── hyva_default.xml
│ │ └── default.xml
│ └── web
│ │ └── js
│ │ └── preload.js
└── base
│ ├── requirejs-config.js
│ └── web
│ └── js
│ ├── ls.native-loading.min.js
│ └── lazysizes.min.js
├── etc
├── module.xml
├── config.xml
├── csp_whitelist.xml
└── adminhtml
│ └── system.xml
├── .gitignore
├── composer.json
├── Scope
└── Config.php
├── Block
└── Preload.php
├── LICENCE.txt
├── CHANGELOG.md
└── README.md
/registration.php:
--------------------------------------------------------------------------------
1 |
8 |
15 |
--------------------------------------------------------------------------------
/etc/module.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/etc/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
11 |
12 |
13 | 1
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/etc/csp_whitelist.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
11 |
12 |
13 | data:
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ############
2 | # Packages #
3 | ############
4 | *.7z
5 | *.dmg
6 | *.gz
7 | *.iso
8 | *.jar
9 | *.rar
10 | *.tar
11 | *.zip
12 |
13 | ###################################
14 | # Logs, databases and other files #
15 | ###################################
16 | *.log
17 | *.sql
18 | *.sqlite
19 | .svn
20 | .thumbs
21 | svg.svg
22 | svg-symbols-demo-page.html
23 |
24 | ######################
25 | # OS generated files #
26 | ######################
27 | .DS_Store
28 | .DS_Store?
29 | ._*
30 | .Spotlight-V100
31 | .Trashes
32 | Icon?
33 | ehthumbs.db
34 | Thumbs.db
35 | *.swp
36 |
37 | ########
38 | # IDEs #
39 | ########
40 | /.idea
41 | /.vscode
42 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fisheye/module-lazyload",
3 | "description": "A Magento 2 module that adds support for lazy loading of images.",
4 | "license": "MIT",
5 | "type": "magento2-module",
6 | "authors": [
7 | {
8 | "name": "Fisheye Media Ltd.",
9 | "homepage": "https://fisheye-webdesign.co.uk/"
10 | },
11 | {
12 | "name": "John Hughes",
13 | "email": "johnh@fisheyehq.com"
14 | }
15 | ],
16 | "require": {
17 | "php": "^7.4 || ^8.0",
18 | "magento/module-catalog": "^104.0"
19 | },
20 | "autoload": {
21 | "psr-4": {
22 | "Fisheye\\Lazyload\\": ""
23 | },
24 | "files": [
25 | "registration.php"
26 | ]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Scope/Config.php:
--------------------------------------------------------------------------------
1 | scopeConfig = $scopeConfig;
24 | }
25 |
26 | public function isImagePreloadingEnabled(): bool
27 | {
28 | return $this->scopeConfig->isSetFlag(
29 | self::XML_PATH_CATALOG_IMAGE_PRELOAD_ENABLED,
30 | ScopeInterface::SCOPE_STORE
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/view/frontend/layout/hyva_default.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/view/base/requirejs-config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © Fisheye Media Ltd. All rights reserved.
3 | * See LICENCE.txt for licence details.
4 | */
5 | var config = {
6 | map: {
7 | '*': {
8 | 'lazysizes': 'Fisheye_Lazyload/js/lazysizes.min',
9 | }
10 | },
11 | deps: [
12 | 'lazysizes',
13 | 'Fisheye_Lazyload/js/ls.native-loading.min',
14 | ],
15 | shim: {
16 | 'Fisheye_Lazyload/js/lazysizes.min': {
17 | // Yes, there is a RequireJS-compatible (UMD) version of lazysizes
18 | // available and this works just fine on its own. However there's a
19 | // bug in Magento's mixins.js that prevents anything using the UMD
20 | // pattern from working at all. Instead, we're using the 'global'
21 | // version of lazysizes and this 'shim' configuration to map it into
22 | // RequireJS. We need to have RequireJS working properly for
23 | // lazysizes plugins to work well.
24 | exports: 'lazySizes',
25 | },
26 | },
27 | };
28 |
--------------------------------------------------------------------------------
/Block/Preload.php:
--------------------------------------------------------------------------------
1 | serializer = $serializer;
31 | }
32 |
33 | /**
34 | * @return string
35 | */
36 | public function getJsConfig(): string
37 | {
38 | return $this->serializer->serialize([
39 | 'preload_selectors' => $this->getData('preload_elements'),
40 | ]);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/LICENCE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Fisheye Media Ltd.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6 |
7 | ## [Unreleased]
8 |
9 | ## [2.1.0] - 2019-07-16
10 | * Added: Magento 2.3 support
11 | * Added: move JS to base area to support admin
12 | * Added: latest lazysizes script
13 |
14 | ## [2.0.0] - 2018-10-01
15 | * Changed: genreal refactoring
16 | * Fixed: module enable / disable check
17 | * Added: HTML attribute / URL escaping to image fields
18 | * Removed: 2.0 / 2.1 support (due to escaping)
19 |
20 | ## [1.0.2] - 2018-02-20
21 | * Fixed: refactor preference to plugin to provide better interoperability with other modules
22 |
23 | ## [1.0.1] - 2018-02-17
24 | * Added: missing licence in `composer.json`
25 |
26 | ## [1.0.0] - 2018-02-15
27 | ### Added
28 | * Added: lazysizes script to support image lazy loading
29 | * Added: automatic lazy loading support for product images
30 | * Added: control over enabling/disabling lazy loading of product images from the Magento admin panel
31 | * Added: 1px x 1px transparent png as placeholder for product images
32 |
--------------------------------------------------------------------------------
/view/frontend/web/js/preload.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © Fisheye Media Ltd. All rights reserved.
3 | * See LICENCE.txt for licence details.
4 | */
5 | define([
6 | 'jquery',
7 | 'lazysizes',
8 | ], function ($, lazySizes) {
9 | 'use strict';
10 |
11 | /**
12 | * @param {Object} config
13 | */
14 | return function (config) {
15 | var selectors,
16 | key,
17 | $preloadElements;
18 |
19 | if (!lazySizes
20 | || !config
21 | || !config.preload_selectors
22 | || $.isEmptyObject(config.preload_selectors)
23 | ) {
24 | // Do nothing if no selectors or lazySizes not found
25 | return;
26 | }
27 |
28 | selectors = config.preload_selectors;
29 |
30 | // For each - node in the 'preload_elements' layout argument
31 | for (key in selectors) {
32 | if (Object.prototype.hasOwnProperty.call(selectors, key)) {
33 | $preloadElements = $(selectors[key]);
34 |
35 | // For each element in the collection, unveil it
36 | $preloadElements.each(function () {
37 | lazySizes.loader.unveil(this);
38 | });
39 | }
40 | }
41 | };
42 | });
43 |
--------------------------------------------------------------------------------
/view/frontend/layout/default.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
11 |
12 |
16 |
17 |
21 |
22 |
- header .lazyload
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/etc/adminhtml/system.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
11 |
12 |
18 |
19 |
27 | Magento\Config\Model\Config\Source\Yesno
28 |
29 |
30 | Load images before they are within the user's viewport.
31 | This will only affect images defined via layout arguments by a developer.
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/view/base/web/js/ls.native-loading.min.js:
--------------------------------------------------------------------------------
1 | /*! lazysizes - v5.3.2 */
2 |
3 | !function(e,t){var n=function(){t(e.lazySizes),e.removeEventListener("lazyunveilread",n,!0)};t=t.bind(null,e,e.document),"object"==typeof module&&module.exports?t(require("lazysizes")):"function"==typeof define&&define.amd?define(["lazysizes"],t):e.lazySizes?n():e.addEventListener("lazyunveilread",n,!0)}(window,function(a,o,r){"use strict";var s="loading"in HTMLImageElement.prototype,d="loading"in HTMLIFrameElement.prototype,l=!1,t=r.prematureUnveil,v=r.cfg,u={focus:1,mouseover:1,click:1,load:1,transitionend:1,animationend:1,scroll:1,resize:1};function n(){var e,t,n;function i(){setTimeout(function(){a.removeEventListener("scroll",e._aLSL,!0)},1e3)}l||(l=!0,s&&d&&v.nativeLoading.disableListeners&&(!0===v.nativeLoading.disableListeners&&(v.nativeLoading.setLoadingAttribute=!0),e=r.loader,t=e.checkElems,(n="object"==typeof v.nativeLoading.disableListeners?v.nativeLoading.disableListeners:u).scroll&&(a.addEventListener("load",i),i(),a.removeEventListener("scroll",t,!0)),n.resize&&a.removeEventListener("resize",t,!0),Object.keys(n).forEach(function(e){n[e]&&o.removeEventListener(e,t,!0)})),v.nativeLoading.setLoadingAttribute&&a.addEventListener("lazybeforeunveil",function(e){var t=e.target;"loading"in t&&!t.getAttribute("loading")&&t.setAttribute("loading","lazy")},!0))}v.nativeLoading||(v.nativeLoading={}),a.addEventListener&&a.MutationObserver&&(s||d)&&(r.prematureUnveil=function(e){return l||n(),!(!("loading"in e&&(v.nativeLoading.setLoadingAttribute||e.getAttribute("loading")))||"auto"==e.getAttribute("data-sizes")&&!e.offsetWidth)||(t?t(e):void 0)})});
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fisheye_Lazyload
2 |
3 | ## Overview
4 | A Magento 2 module that adds support for lazy loading of images.
5 |
6 | ## Features
7 |
8 | * Utilises the [lazysizes](https://github.com/aFarkas/lazysizes) script to add lazy load support for images
9 | * Lazy loading is automatically applied to all\* product images
10 | * \*Any image created via `Magento\Catalog\Block\Product\Image`
11 | * This includes:
12 | * Product listing (category, search, product list widget)
13 | * Product relations (related, upsell, crosssell)
14 | * Product comparison
15 | * Recently viewed
16 | * Cart thumbnails
17 | * Wishlist
18 | * Support can be added to any further images by adding the following to `
` elements:
19 | * Add `lazyload` class
20 | * Change the `src` attribute to `data-src`
21 | * Optional: add a dummy `src` with transparent 1px x 1px image to avoid broken image links prior to lazy load (see product image templates as example)
22 | * Optional: add `loading="lazy"` to enable native browser lazy-loading where possible. Note that this is not true native lazy-loading, as the `lazysizes` script and its `native-loading` plug-in need to be run, so there's still a dependency on JavaScript before affected images will show.
23 | * Preloading
24 | * Preloading means the media source will be loaded even if not in the viewport such as megamenu icons/images.
25 | * The `preload_elements` layout argument allows you to specify selectors of which elements can be preloaded. This can be configured per page.
26 | * Use the `lazypreload` class on element(s) you wish to preload.
27 |
28 | ## Compatibility
29 |
30 | * Magento Community Edition / Enterprise Edition 2.4.x (see older releases for 2.0 - 2.3 support)
31 | * Supports Magento 2 Full Page Cache (including Varnish)
32 |
33 | ## Installation
34 |
35 | ```
36 | composer require fisheye/module-lazyload
37 | php bin/magento module:enable Fisheye_Lazyload
38 | php bin/magento setup:upgrade
39 | ```
40 |
41 | ## Contributing
42 | Issues, forks and pull requests welcomed :)
43 |
--------------------------------------------------------------------------------
/view/base/web/js/lazysizes.min.js:
--------------------------------------------------------------------------------
1 | /*! lazysizes - v5.3.2 */
2 | !function(e){var t=function(u,D,f){"use strict";var k,H;if(function(){var e;var t={lazyClass:"lazyload",loadedClass:"lazyloaded",loadingClass:"lazyloading",preloadClass:"lazypreload",errorClass:"lazyerror",autosizesClass:"lazyautosizes",fastLoadedClass:"ls-is-cached",iframeLoadMode:0,srcAttr:"data-src",srcsetAttr:"data-srcset",sizesAttr:"data-sizes",minSize:40,customMedia:{},init:true,expFactor:1.5,hFac:.8,loadMode:2,loadHidden:true,ricTimeout:0,throttleDelay:125};H=u.lazySizesConfig||u.lazysizesConfig||{};for(e in t){if(!(e in H)){H[e]=t[e]}}}(),!D||!D.getElementsByClassName){return{init:function(){},cfg:H,noSupport:true}}var O=D.documentElement,i=u.HTMLPictureElement,P="addEventListener",$="getAttribute",q=u[P].bind(u),I=u.setTimeout,U=u.requestAnimationFrame||I,o=u.requestIdleCallback,j=/^picture$/i,r=["load","error","lazyincluded","_lazyloaded"],a={},G=Array.prototype.forEach,J=function(e,t){if(!a[t]){a[t]=new RegExp("(\\s|^)"+t+"(\\s|$)")}return a[t].test(e[$]("class")||"")&&a[t]},K=function(e,t){if(!J(e,t)){e.setAttribute("class",(e[$]("class")||"").trim()+" "+t)}},Q=function(e,t){var a;if(a=J(e,t)){e.setAttribute("class",(e[$]("class")||"").replace(a," "))}},V=function(t,a,e){var i=e?P:"removeEventListener";if(e){V(t,a)}r.forEach(function(e){t[i](e,a)})},X=function(e,t,a,i,r){var n=D.createEvent("Event");if(!a){a={}}a.instance=k;n.initEvent(t,!i,!r);n.detail=a;e.dispatchEvent(n);return n},Y=function(e,t){var a;if(!i&&(a=u.picturefill||H.pf)){if(t&&t.src&&!e[$]("srcset")){e.setAttribute("srcset",t.src)}a({reevaluate:true,elements:[e]})}else if(t&&t.src){e.src=t.src}},Z=function(e,t){return(getComputedStyle(e,null)||{})[t]},s=function(e,t,a){a=a||e.offsetWidth;while(a49?function(){o(t,{timeout:n});if(n!==H.ricTimeout){n=H.ricTimeout}}:te(function(){I(t)},true);return function(e){var t;if(e=e===true){n=33}if(a){return}a=true;t=r-(f.now()-i);if(t<0){t=0}if(e||t<9){s()}else{I(s,t)}}},ie=function(e){var t,a;var i=99;var r=function(){t=null;e()};var n=function(){var e=f.now()-a;if(e0;if(r&&Z(i,"overflow")!="visible"){a=i.getBoundingClientRect();r=C>a.left&&pa.top-1&&g500&&O.clientWidth>500?500:370:H.expand;k._defEx=u;f=u*H.expFactor;c=H.hFac;A=null;if(w2&&h>2&&!D.hidden){w=f;N=0}else if(h>1&&N>1&&M<6){w=u}else{w=_}}if(l!==n){y=innerWidth+n*c;z=innerHeight+n;s=n*-1;l=n}a=d[t].getBoundingClientRect();if((b=a.bottom)>=s&&(g=a.top)<=z&&(C=a.right)>=s*c&&(p=a.left)<=y&&(b||C||p||g)&&(H.loadHidden||x(d[t]))&&(m&&M<3&&!o&&(h<3||N<4)||W(d[t],n))){R(d[t]);r=true;if(M>9){break}}else if(!r&&m&&!i&&M<4&&N<4&&h>2&&(v[0]||H.preloadAfterLoad)&&(v[0]||!o&&(b||C||p||g||d[t][$](H.sizesAttr)!="auto"))){i=v[0]||d[t]}}if(i&&!r){R(i)}}};var a=ae(t);var S=function(e){var t=e.target;if(t._lazyCache){delete t._lazyCache;return}L(e);K(t,H.loadedClass);Q(t,H.loadingClass);V(t,B);X(t,"lazyloaded")};var i=te(S);var B=function(e){i({target:e.target})};var T=function(e,t){var a=e.getAttribute("data-load-mode")||H.iframeLoadMode;if(a==0){e.contentWindow.location.replace(t)}else if(a==1){e.src=t}};var F=function(e){var t;var a=e[$](H.srcsetAttr);if(t=H.customMedia[e[$]("data-media")||e[$]("media")]){e.setAttribute("media",t)}if(a){e.setAttribute("srcset",a)}};var s=te(function(t,e,a,i,r){var n,s,o,l,u,f;if(!(u=X(t,"lazybeforeunveil",e)).defaultPrevented){if(i){if(a){K(t,H.autosizesClass)}else{t.setAttribute("sizes",i)}}s=t[$](H.srcsetAttr);n=t[$](H.srcAttr);if(r){o=t.parentNode;l=o&&j.test(o.nodeName||"")}f=e.firesLoad||"src"in t&&(s||n||l);u={target:t};K(t,H.loadingClass);if(f){clearTimeout(c);c=I(L,2500);V(t,B,true)}if(l){G.call(o.getElementsByTagName("source"),F)}if(s){t.setAttribute("srcset",s)}else if(n&&!l){if(d.test(t.nodeName)){T(t,n)}else{t.src=n}}if(r&&(s||l)){Y(t,{src:n})}}if(t._lazyRace){delete t._lazyRace}Q(t,H.lazyClass);ee(function(){var e=t.complete&&t.naturalWidth>1;if(!f||e){if(e){K(t,H.fastLoadedClass)}S(u);t._lazyCache=true;I(function(){if("_lazyCache"in t){delete t._lazyCache}},9)}if(t.loading=="lazy"){M--}},true)});var R=function(e){if(e._lazyRace){return}var t;var a=n.test(e.nodeName);var i=a&&(e[$](H.sizesAttr)||e[$]("sizes"));var r=i=="auto";if((r||!m)&&a&&(e[$]("src")||e.srcset)&&!e.complete&&!J(e,H.errorClass)&&J(e,H.lazyClass)){return}t=X(e,"lazyunveilread").detail;if(r){re.updateElem(e,true,e.offsetWidth)}e._lazyRace=true;M++;s(e,t,r,i,a)};var r=ie(function(){H.loadMode=3;a()});var o=function(){if(H.loadMode==3){H.loadMode=2}r()};var l=function(){if(m){return}if(f.now()-e<999){I(l,999);return}m=true;H.loadMode=3;a();q("scroll",o,true)};return{_:function(){e=f.now();k.elements=D.getElementsByClassName(H.lazyClass);v=D.getElementsByClassName(H.lazyClass+" "+H.preloadClass);q("scroll",a,true);q("resize",a,true);q("pageshow",function(e){if(e.persisted){var t=D.querySelectorAll("."+H.loadingClass);if(t.length&&t.forEach){U(function(){t.forEach(function(e){if(e.complete){R(e)}})})}}});if(u.MutationObserver){new MutationObserver(a).observe(O,{childList:true,subtree:true,attributes:true})}else{O[P]("DOMNodeInserted",a,true);O[P]("DOMAttrModified",a,true);setInterval(a,999)}q("hashchange",a,true);["focus","mouseover","click","load","transitionend","animationend"].forEach(function(e){D[P](e,a,true)});if(/d$|^c/.test(D.readyState)){l()}else{q("load",l);D[P]("DOMContentLoaded",a);I(l,2e4)}if(k.elements.length){t();ee._lsFlush()}else{a()}},checkElems:a,unveil:R,_aLSL:o}}(),re=function(){var a;var n=te(function(e,t,a,i){var r,n,s;e._lazysizesWidth=i;i+="px";e.setAttribute("sizes",i);if(j.test(t.nodeName||"")){r=t.getElementsByTagName("source");for(n=0,s=r.length;n