├── Block ├── Adminhtml │ └── System │ │ └── Config │ │ └── Form │ │ ├── DynamicRow.php │ │ └── Info.php └── Lazy.php ├── LICENSE.txt ├── Model ├── Config.php └── Config │ └── Source │ ├── BlocksToLazyLoad.php │ └── Method.php ├── Observer └── LayoutLoadBeforeObserver.php ├── Plugin ├── Amasty │ └── PageSpeedOptimizer │ │ └── Model │ │ └── Output │ │ └── LazyLoadProcessorPlugin.php ├── BlockPlugin.php ├── Magefan │ └── WebP │ │ ├── Api │ │ └── HtmlParserInterfacePlugin.php │ │ └── Model │ │ └── WebPPlugin.php └── Magento │ └── PageBuilder │ └── Model │ └── Filter │ └── Template.php ├── README.md ├── Setup └── Patch │ └── Data │ └── ConvertConfigToJsonPatch.php ├── composer.json ├── etc ├── acl.xml ├── adminhtml │ └── system.xml ├── config.xml ├── di.xml ├── frontend │ ├── di.xml │ └── events.xml └── module.xml ├── registration.php └── view ├── adminhtml ├── pagebuilder │ └── content_type │ │ └── image.xml └── ui_component │ └── pagebuilder_image_form.xml └── frontend ├── layout ├── default.xml └── mflazyzoad_no_js.xml ├── templates └── lazy.phtml └── web ├── images └── pixel.jpg └── js └── lazyload.min.js /Block/Adminhtml/System/Config/Form/DynamicRow.php: -------------------------------------------------------------------------------- 1 | addColumn('block_identifier', ['label' => __('Block Identifier'), 'style' => 'width:350px']); 23 | $this->addColumn('first_images_to_skip', ['label' => __('First Images To Skip'), 'style' => 'width:70px']); 24 | $this->_addAfter = false; 25 | $this->_addButtonLabel = __('Add'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Block/Adminhtml/System/Config/Form/Info.php: -------------------------------------------------------------------------------- 1 | config = $config; 34 | } 35 | 36 | /** 37 | * Retrieve block html 38 | * 39 | * @return string 40 | */ 41 | protected function _toHtml() 42 | { 43 | if ($this->config->getEnabled()) { 44 | return parent::_toHtml(); 45 | } 46 | 47 | return ''; 48 | } 49 | 50 | /** 51 | * @return bool 52 | */ 53 | public function isNoScriptEnabled(): bool 54 | { 55 | return (bool)$this->config->isNoScriptEnabled(); 56 | } 57 | 58 | /** 59 | * @return bool 60 | */ 61 | public function getIsJavascriptLazyLoadMethod(): bool 62 | { 63 | return $this->config->getIsJavascriptLazyLoadMethod(); 64 | } 65 | 66 | /** 67 | * Retrieve lazy load config json string 68 | * 69 | * @return string 70 | */ 71 | public function getLazyLoadConfig(): string 72 | { 73 | $config = $this->getData('lazy_load_config'); 74 | 75 | if (!is_array($config)) { 76 | $config = []; 77 | } 78 | 79 | if (!isset($config['elements_selector'])) { 80 | $config['elements_selector'] = 'img,div'; 81 | } 82 | 83 | if (!isset($config['data_srcset'])) { 84 | $config['data_srcset'] = 'originalset'; 85 | } 86 | 87 | return json_encode($config); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Please visit Magefan.com for license details (https://magefan.com/end-user-license-agreement) -------------------------------------------------------------------------------- /Model/Config.php: -------------------------------------------------------------------------------- 1 | serializer = $serializer; 54 | parent::__construct($context); 55 | } 56 | 57 | /** 58 | * Retrieve store config value 59 | * @param string $path 60 | * @return mixed 61 | */ 62 | public function getConfig($path) 63 | { 64 | return $this->scopeConfig->getValue( 65 | $path, 66 | \Magento\Store\Model\ScopeInterface::SCOPE_STORE 67 | ); 68 | } 69 | 70 | /** 71 | * @return bool 72 | */ 73 | public function getIsJavascriptLazyLoadMethod(): bool 74 | { 75 | return (Method::JAVASCRIPT === (int)$this->getConfig(self::XML_PATH_LAZY_METHOD)); 76 | } 77 | 78 | /** 79 | * @return bool 80 | */ 81 | public function isAllBlocksAddedToLazy(): bool 82 | { 83 | return (BlocksToLazyLoad::ALL === (int)$this->getConfig(self::XML_PATH_LAZY_BLOCKS_TO_LAZY_LOAD)); 84 | } 85 | 86 | /** 87 | * @return bool 88 | */ 89 | public function isNoScriptEnabled(): bool 90 | { 91 | return (bool)$this->getConfig(self::XML_PATH_LAZY_NOSCRIPT) 92 | && $this->getIsJavascriptLazyLoadMethod(); 93 | } 94 | 95 | /** 96 | * Retrieve alloved blocks info 97 | * @return array 98 | */ 99 | public function getBlocks(): array 100 | { 101 | return array_keys($this->getBlocksInfo()); 102 | } 103 | 104 | /** 105 | * @param $blockIdentifier 106 | * @return int 107 | */ 108 | public function getBlockFirstImagesToSkip($blockIdentifier): int 109 | { 110 | $blockInfo = $this->getBlocksInfo(); 111 | if (isset($blockInfo[$blockIdentifier])) { 112 | return (int)$blockInfo[$blockIdentifier]; 113 | } 114 | 115 | return 0; 116 | } 117 | 118 | /** 119 | * @return array 120 | */ 121 | public function getBlocksInfo(): array 122 | { 123 | if (null === $this->blocks) { 124 | $this->blocks = []; 125 | 126 | try { 127 | $blocks = $this->serializer->unserialize($this->getConfig(self::XML_PATH_LAZY_BLOCKS)); 128 | } catch (\InvalidArgumentException $e) { 129 | return $this->blocks; 130 | } 131 | 132 | foreach ($blocks as $blockData) { 133 | if (!isset($blockData['block_identifier']) || !isset($blockData['first_images_to_skip'])) { 134 | continue; 135 | } 136 | 137 | $this->blocks[$blockData['block_identifier']] = $blockData['first_images_to_skip']; 138 | } 139 | } 140 | 141 | return $this->blocks; 142 | } 143 | 144 | /** 145 | * Retrieve true if enabled 146 | * @return bool 147 | */ 148 | public function getEnabled(): bool 149 | { 150 | if (null === $this->enabled) { 151 | $this->enabled = (bool)$this->getConfig(self::XML_PATH_ENABLED); 152 | 153 | /* check if Plumrocket AMP enabled */ 154 | if ($this->enabled) { 155 | /* We know that using objectManager is not a not a good practice, 156 | but if Plumrocket_AMP is not installed on your magento instance 157 | you'll get error during di:compile */ 158 | $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); 159 | 160 | $isAmpRequest = $this->getConfig(self::XML_PATH_AMP_ENABLED); 161 | if ($isAmpRequest) { 162 | $isAmpRequest = $objectManager->get('\Plumrocket\Amp\Helper\Data') 163 | ->isAmpRequest(); 164 | } else { 165 | $isAmpRequest = $objectManager->get(\Magento\Framework\App\RequestInterface::class) 166 | ->getParam('is_amp'); 167 | } 168 | $this->enabled = !$isAmpRequest; 169 | } 170 | } 171 | 172 | return $this->enabled; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Model/Config/Source/BlocksToLazyLoad.php: -------------------------------------------------------------------------------- 1 | self::ALL, 'label' => __('All')], 25 | ['value' => self::SELECTED, 'label' => __('Selected')], 26 | ]; 27 | } 28 | 29 | /** 30 | * Get options in "key-value" format 31 | * 32 | * @return array 33 | */ 34 | public function toArray(): array 35 | { 36 | $array = []; 37 | foreach ($this->toOptionArray() as $item) { 38 | $array[$item['value']] = $item['label']; 39 | } 40 | return $array; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Model/Config/Source/Method.php: -------------------------------------------------------------------------------- 1 | self::JAVASCRIPT, 'label' => __('Non-jQuery JavaScript Library (Requires Advanced Configuration)')], 37 | ['value' => self::NATIVE, 'label' => __('Native Browser Lazy Loading')], 38 | ]; 39 | } 40 | 41 | /** 42 | * Get options in "key-value" format 43 | * 44 | * @return array 45 | */ 46 | public function toArray(): array 47 | { 48 | $array = []; 49 | foreach ($this->toOptionArray() as $item) { 50 | $array[$item['value']] = $item['label']; 51 | } 52 | return $array; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Observer/LayoutLoadBeforeObserver.php: -------------------------------------------------------------------------------- 1 | config = $config; 34 | } 35 | 36 | /** 37 | * @param \Magento\Framework\Event\Observer $observer 38 | * @return void 39 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 40 | */ 41 | public function execute(\Magento\Framework\Event\Observer $observer) 42 | { 43 | if ($this->config->getEnabled() && $this->config->isNoScriptEnabled()) { 44 | $layout = $observer->getLayout(); 45 | $layout->getUpdate()->addHandle('mflazyzoad_no_js'); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Plugin/Amasty/PageSpeedOptimizer/Model/Output/LazyLoadProcessorPlugin.php: -------------------------------------------------------------------------------- 1 | config = $config; 33 | } 34 | 35 | /** 36 | * @param $subject 37 | * @param callable $proceed 38 | * @param $image 39 | * @param $imagePath 40 | * @return mixed|null|string|string[] 41 | */ 42 | public function aroundReplaceWithPictureTag($subject, callable $proceed, $image, $imagePath) 43 | { 44 | if (!$this->config->getEnabled() || !$this->config->getIsJavascriptLazyLoadMethod()) { 45 | return $proceed($image, $imagePath); 46 | } 47 | 48 | $originImagePath = $imagePath; 49 | 50 | if (strpos($imagePath, 'Magefan_LazyLoad/images/pixel.jpg')) { 51 | 52 | $doStr = 'data-original="'; 53 | $p1 = strpos($image, $doStr); 54 | 55 | if ($p1 !== false) { 56 | $p1 += strlen($doStr); 57 | $p2 = strpos($image, '"', $p1); 58 | if ($p2 !== false) { 59 | $imagePath = substr($image, $p1, $p2 - $p1); 60 | } 61 | } 62 | } 63 | 64 | $html = $proceed($image, $imagePath); 65 | 66 | if ($originImagePath != $imagePath) { 67 | 68 | if (strpos($html, ']*)(?:srcset="([^"]*)")([^>]*)?>#isU', '', $html); 76 | 77 | $html = str_replace($tmpSrc, $pixelSrc, $html); 78 | } 79 | } 80 | 81 | return $html; 82 | } 83 | 84 | /** 85 | * @param $subject 86 | * @param callable $proceed 87 | * @param algorithm 88 | * @param $image 89 | * @param $imagePath 90 | * @return mixed|null|string|string[] 91 | */ 92 | public function aroundReplace($subject, callable $proceed, $algorithm, $image, $imagePath) 93 | { 94 | 95 | if (!$this->config->getEnabled() || !$this->config->getIsJavascriptLazyLoadMethod()) { 96 | return $proceed($algorithm, $image, $imagePath); 97 | } 98 | 99 | $originImagePath = $imagePath; 100 | 101 | if (strpos($imagePath, 'Magefan_LazyLoad/images/pixel.jpg')) { 102 | 103 | $doStr = 'data-original="'; 104 | $p1 = strpos($image, $doStr); 105 | 106 | if ($p1 !== false) { 107 | $p1 += strlen($doStr); 108 | $p2 = strpos($image, '"', $p1); 109 | if ($p2 !== false) { 110 | $imagePath = substr($image, $p1, $p2 - $p1); 111 | } 112 | } 113 | } 114 | 115 | $html = $proceed($algorithm, $image, $imagePath); 116 | 117 | if ($originImagePath != $imagePath) { 118 | 119 | if (strpos($html, ']*)(?:srcset="([^"]*)")([^>]*)?>#isU', '', $html); 127 | 128 | $html = str_replace($tmpSrc, $pixelSrc, $html); 129 | } 130 | } 131 | 132 | return $html; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Plugin/BlockPlugin.php: -------------------------------------------------------------------------------- 1 | '; 21 | 22 | const REPLACEMENT_LABEL = 'mf-lazy'; 23 | 24 | const PATTERN = '#]*)(?:\ssrc="([^"]*)")([^>]*)\/?>#isU'; 25 | 26 | /** 27 | * Request 28 | * @var \Magento\Framework\App\RequestInterface 29 | */ 30 | protected $request; 31 | 32 | /** 33 | * Core store config 34 | * 35 | * @var \Magento\Framework\App\Config\ScopeConfigInterface 36 | */ 37 | protected $scopeConfig; 38 | 39 | /** 40 | * @var array 41 | */ 42 | protected $blocks; 43 | 44 | /** 45 | * Lazy store config 46 | * 47 | * @var \Magefan\LazyLoad\Model\Config 48 | */ 49 | protected $config; 50 | 51 | /** 52 | * @var array 53 | */ 54 | protected $skipBlocks = []; 55 | 56 | /** 57 | * @param \Magento\Framework\App\RequestInterface $request 58 | * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig 59 | * @param Config $config 60 | * @param array $skipBlocks 61 | */ 62 | public function __construct( 63 | \Magento\Framework\App\RequestInterface $request, 64 | \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, 65 | Config $config, 66 | array $skipBlocks 67 | ) { 68 | $this->request = $request; 69 | $this->scopeConfig = $scopeConfig; 70 | $this->config = $config; 71 | $this->skipBlocks = $skipBlocks; 72 | } 73 | 74 | /** 75 | * @param \Magento\Framework\View\Element\AbstractBlock $block 76 | * @param string $html 77 | * @return string 78 | */ 79 | public function afterToHtml(\Magento\Framework\View\Element\AbstractBlock $block, $html) 80 | { 81 | if (!$html || !$this->isEnabled($block, (string)$html)) { 82 | return $html; 83 | } 84 | 85 | $numberOfReplacements = $this->config->getBlockFirstImagesToSkip($this->getBlockIdentifier($block)); 86 | 87 | if ($numberOfReplacements) { 88 | $html = $this->removeLoadingLazyAttributeFromFirstNImages($html, $numberOfReplacements); 89 | } 90 | 91 | $html = $this->config->getIsJavascriptLazyLoadMethod() 92 | ? $this->prepareForJsLazyLoad($block, $html) 93 | : $this->prepareForNativeBrowserLazyLoad($html); 94 | 95 | return $html; 96 | } 97 | 98 | 99 | /** 100 | * @param string $html 101 | * @return string 102 | */ 103 | private function prepareForJsLazyLoad($block, string $html): string 104 | { 105 | $lazyAttribute = ' data-original='; 106 | 107 | $pixelSrc = ' src="' . $block->getViewFileUrl('Magefan_LazyLoad::images/pixel.jpg') . '"'; 108 | $tmpSrc = 'TMP_SRC'; 109 | $tmpDataOriginal = 'TMP_DATA_ORIGINAL'; 110 | 111 | $html = str_replace($pixelSrc, $tmpSrc, $html); 112 | $html = str_replace($lazyAttribute, $tmpDataOriginal, $html); 113 | 114 | $noscript = ''; 115 | if ($this->config->isNoScriptEnabled()) { 116 | $noscript = ''; 119 | } 120 | 121 | $html = preg_replace( 122 | self::PATTERN, 123 | '' . $noscript, 124 | $html 125 | ); 126 | 127 | $html = str_replace($lazyAttribute, $pixelSrc . $lazyAttribute, $html); 128 | 129 | $html = str_replace($tmpSrc, $pixelSrc, $html); 130 | $html = str_replace($tmpDataOriginal, $lazyAttribute, $html); 131 | $html = str_replace(self::LAZY_TAG, '', $html); 132 | 133 | /* Disable Owl Slider LazyLoad */ 134 | $html = str_replace( 135 | ['"lazyLoad":true,', '"lazyLoad":true,', 'owl-lazy'], 136 | ['"lazyLoad":false,', '"lazyLoad":false,', ''], 137 | $html 138 | ); 139 | 140 | /* Fix for page builder bg images */ 141 | if (false !== strpos($html, 'background-image-')) { 142 | $html = str_replace('.background-image-', '.tmpbgimg-', $html); 143 | $html = str_replace('background-image-', 'mflazy-background-image mflazy-background-image-', $html); 144 | $html = str_replace('.tmpbgimg-', '.background-image-', $html); 145 | } 146 | 147 | return $html; 148 | } 149 | 150 | /** 151 | * @param string $html 152 | * @return string 153 | */ 154 | protected function prepareForNativeBrowserLazyLoad(string $html) :string 155 | { 156 | return preg_replace(self::PATTERN, '', $html); 157 | } 158 | 159 | /** 160 | * @param \Magento\Framework\View\Element\AbstractBlock $block 161 | * @return string 162 | */ 163 | protected function getBlockIdentifier(\Magento\Framework\View\Element\AbstractBlock $block): string 164 | { 165 | $blockName = $block->getBlockId() ?: $block->getNameInLayout(); 166 | $blockTemplate = $block->getTemplate(); 167 | $blocks = $this->config->getBlocks(); 168 | 169 | if (in_array($blockName, $blocks)) { 170 | return $blockName; 171 | } elseif (in_array(get_class($block), $blocks)) { 172 | return get_class($block); 173 | } elseif (in_array($blockTemplate, $blocks)) { 174 | return $blockTemplate; 175 | } 176 | 177 | return ''; 178 | } 179 | 180 | /** 181 | * @param string $html 182 | * @param int $numberOfReplacements 183 | * @return string 184 | */ 185 | protected function removeLoadingLazyAttributeFromFirstNImages(string $html, int $numberOfReplacements):string 186 | { 187 | $position = 0; 188 | 189 | if (preg_match_all(self::PATTERN, $html, $matches, PREG_OFFSET_CAPTURE) !== false) { 190 | foreach ($matches[0] as $i => $match) { 191 | if ($i > $numberOfReplacements - 1) { 192 | break; 193 | } 194 | 195 | $offset = $match[1] + $position; 196 | $htmlTag = $matches[0][$i][0]; 197 | 198 | $newHtmlTag = str_replace( 199 | ['loading="lazy"', 'request->isXmlHttpRequest() 222 | || false !== stripos($this->request->getFullActionName(), 'ajax') 223 | || false !== stripos($this->request->getServer('REQUEST_URI'), 'layerednavigationajax') 224 | || $this->request->getParam('isAjax') 225 | ) { 226 | return false; 227 | } 228 | 229 | if (!$this->config->getEnabled()) { 230 | return false; 231 | } 232 | 233 | if ($this->config->isAllBlocksAddedToLazy() && !$this->isBlockSkipped($block)) { 234 | return true; 235 | } 236 | 237 | if (false !== strpos($html, self::LAZY_TAG)) { 238 | return true; 239 | } 240 | 241 | if (!$this->getBlockIdentifier($block)) { 242 | return false; 243 | } 244 | 245 | return true; 246 | } 247 | 248 | /** 249 | * @param $block 250 | * @return bool 251 | */ 252 | private function isBlockSkipped($block): bool 253 | { 254 | return in_array(get_class($block), $this->skipBlocks); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /Plugin/Magefan/WebP/Api/HtmlParserInterfacePlugin.php: -------------------------------------------------------------------------------- 1 | config = $config; 34 | } 35 | 36 | /** 37 | * @param $subject 38 | * @param callable $proceed 39 | * @param $imagePath 40 | * @param $htmlTag 41 | * @return mixed|string|string[]|null 42 | */ 43 | public function aroundGetNewHtmlTag($subject, callable $proceed, $imagePath, $image) 44 | { 45 | if (!$this->config->getEnabled() || !$this->config->getIsJavascriptLazyLoadMethod()) { 46 | return $proceed($imagePath, $image); 47 | } 48 | 49 | $originImagePath = $imagePath; 50 | 51 | if (strpos($imagePath, 'Magefan_LazyLoad/images/pixel.jpg')) { 52 | 53 | $doStr = 'data-original="'; 54 | $p1 = strpos($image, $doStr); 55 | 56 | if ($p1 !== false) { 57 | $p1 += strlen($doStr); 58 | $p2 = strpos($image, '"', $p1); 59 | if ($p2 !== false) { 60 | $imagePath = substr($image, $p1, $p2 - $p1); 61 | } 62 | } 63 | } 64 | 65 | $html = $proceed($imagePath, $image); 66 | 67 | if ($originImagePath != $imagePath) { 68 | 69 | if (strpos($html, ']*)(?:\ssrcset="([^"]*)")([^>]*)?>#isU', '', $html); 77 | 78 | $html = str_replace($tmpSrc, $pixelSrc, $html); 79 | } 80 | } 81 | 82 | return $html; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Plugin/Magefan/WebP/Model/WebPPlugin.php: -------------------------------------------------------------------------------- 1 | config = $config; 34 | } 35 | 36 | /** 37 | * @param $subject 38 | * @param callable $proceed 39 | * @param $imagePath 40 | * @param $htmlTag 41 | * @return mixed|string|string[]|null 42 | */ 43 | public function aroundGetPictureTagHtml($subject, callable $proceed, $imagePath, $image) 44 | { 45 | if (!$this->config->getEnabled() || !$this->config->getIsJavascriptLazyLoadMethod()) { 46 | return $proceed($imagePath, $image); 47 | } 48 | 49 | $originImagePath = $imagePath; 50 | 51 | if (strpos($imagePath, 'Magefan_LazyLoad/images/pixel.jpg')) { 52 | 53 | $doStr = 'data-original="'; 54 | $p1 = strpos($image, $doStr); 55 | 56 | if ($p1 !== false) { 57 | $p1 += strlen($doStr); 58 | $p2 = strpos($image, '"', $p1); 59 | if ($p2 !== false) { 60 | $imagePath = substr($image, $p1, $p2 - $p1); 61 | } 62 | } 63 | } 64 | 65 | $html = $proceed($imagePath, $image); 66 | 67 | if ($originImagePath != $imagePath) { 68 | 69 | if (strpos($html, ']*)(?:\ssrcset="([^"]*)")([^>]*)?>#isU', '', $html); 77 | 78 | $html = str_replace($tmpSrc, $pixelSrc, $html); 79 | } 80 | } 81 | 82 | return $html; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Plugin/Magento/PageBuilder/Model/Filter/Template.php: -------------------------------------------------------------------------------- 1 | config = $config; 29 | } 30 | 31 | /** 32 | * @param $subject 33 | * @param $result 34 | * @return mixed|string 35 | */ 36 | public function afterFilter($subject, $result) 37 | { 38 | if ($this->config->getEnabled()) { 39 | $this->moveMfDislazyAttributeDirectAfterImg($result); 40 | } 41 | 42 | return $result; 43 | } 44 | 45 | /** 46 | * @param string $result 47 | * @return void 48 | */ 49 | private function moveMfDislazyAttributeDirectAfterImg(string &$result) 50 | { 51 | if (strpos($result, 'mfdislazy="1"') !== false) { 52 | $result = explode(' $imgStart) { 55 | if (strpos($imgStart, 'mfdislazy="1"') !== false) { 56 | $result[$key] = 'mfdislazy="1" ' . str_replace('mfdislazy="1"', '', $imgStart); 57 | } 58 | } 59 | 60 | $result = implode(' 8 | 9 | 10 | 11 | 12 | ## Features 13 | * Allow to load images on your store only when customer can see them. Module reduces page size and number of request. 14 | 15 | 16 | ## Useful Links 17 | * [User Guide](https://magefan.com/magento-2-image-lazy-load-extension/documentation) 18 | * [Change Log](https://magefan.com/magento-2-image-lazy-load-extension/change-log) 19 | * [FAQs](https://magefan.com/magento-2-image-lazy-load-extension#pattr-faq) 20 | 21 | ## Compatibility 22 | * [Magento 2 WebP Optimized Images Extension](https://magefan.com/magento-2-webp-optimized-images) 23 | 24 | ## Requirements 25 | * Magento Community Edition 2.0.x-2.4.x or Magento Enterprise Edition 2.0.x-2.4.x 26 | 27 | ## Demo 28 | 29 | Try out our open demo and if you like our extension **please give us some star on Github ★** 30 | 31 | 32 | 33 | 36 | 39 | 40 | 41 | 50 | 59 | 60 | 61 | 66 | 71 | 72 | 73 |
34 | Storefront Demo 35 | 37 | Admin Panel Demo 38 |
42 | 43 | Magento 2 Image Lazy Load Extension Storefront Demo 48 | 49 | 51 | 52 | Magento 2 Image Lazy Load Extension Admin Panel Demo 57 | 58 |
62 | 63 | view 64 | 65 | 67 | 68 | view 69 | 70 |
74 | 75 | 76 | 77 | ## Support 78 | If you have any issues, please [contact us](mailto:support@magefan.com) 79 | then if you still need help, open a bug report in GitHub's 80 | [issue tracker](https://github.com/magefan/module-lazyload/issues). 81 | 82 | Please do not use Magento Marketplace's Reviews or (especially) the Q&A for support. 83 | There isn't a way for us to reply to reviews and the Q&A moderation is very slow. 84 | 85 | ## License 86 | The code is licensed under [EULA](https://magefan.com/end-user-license-agreement). 87 | 88 | ## [Magento 2 Extensions](https://magefan.com/magento-2-extensions) by Magefan 89 | 90 | ### [Magento 2 Google Extensions](https://magefan.com/magento-2-extensions/google-extensions) 91 | 92 | * [Magento 2 Google Indexing](https://magefan.com/magento-2-google-indexing-api) 93 | * [Magento 2 Google Analytics 4](https://magefan.com/magento-2-google-analytics-4) 94 | * [Magento 2 Google Tag Manager](https://magefan.com/magento-2-google-tag-manager) 95 | * [Magento 2 Google Shopping Feed](https://magefan.com/magento-2-google-shopping-feed-extension) 96 | * [Magento 2 Google Customer Reviews](https://magefan.com/magento-2-google-customer-reviews) 97 | 98 | ### Magento 2 SEO Extensions 99 | 100 | * [Magento 2 SEO Extension](https://magefan.com/magento-2-seo-extension) 101 | * [Magento 2 Rich Snippets](https://magefan.com/magento-2-rich-snippets) 102 | * [Magento 2 HTML Sitemap](https://magefan.com/magento-2-html-sitemap-extension) 103 | * [Magento 2 XML Sitemap](https://magefan.com/magento-2-xml-sitemap-extension) 104 | * [Magento 2 Facebook Open Graph](https://magefan.com/magento-2-open-graph-extension-og-tags) 105 | * [Magento 2 Twitter Cards](https://magefan.com/magento-2-twitter-cards-extension) 106 | 107 | 108 | ### [Magento 2 Speed Optimization Extensions](https://magefan.com/magento-2-extensions/speed-optimization) 109 | 110 | * [Magento 2 Google Page Speed Optimizer](https://magefan.com/magento-2-google-page-speed-optimizer) 111 | * [Magento 2 Full Page Cache Warmer](https://magefan.com/magento-2-full-page-cache-warmer) 112 | * [Magento 2 WebP Images](https://magefan.com/magento-2-webp-optimized-images) 113 | * [Magento 2 Rocket JavaScript](https://magefan.com/rocket-javascript-deferred-javascript) 114 | 115 | ### [Magento 2 Admin Panel Extensions](https://magefan.com/magento-2-extensions/admin-extensions) 116 | 117 | * [Magento 2 Size Chart Extension](https://magefan.com/magento-2-size-chart) 118 | * [Magento 2 Security Extension](https://magefan.com/magento-2-security-extension) 119 | * [Magento 2 Admin Action Log](https://magefan.com/magento-2-admin-action-log) 120 | * [Magento 2 Order Editor](https://magefan.com/magento-2-edit-order-extension) 121 | * [Magento 2 Better Order Grid](https://magefan.com/magento-2-better-order-grid-extension) 122 | * [Magento 2 Extended Product Grid](https://magefan.com/magento-2-product-grid-inline-editor) 123 | * [Magento 2 Product Tabs](https://magefan.com/magento-2/extensions/product-tabs) 124 | * [Magento 2 Facebook Pixel](https://magefan.com/magento-2-facebook-pixel-extension) 125 | * [Magento 2 Email Attachments](https://magefan.com/magento-2-email-attachments) 126 | * [Magento 2 Admin View](https://magefan.com/magento-2-admin-view-extension) 127 | * [Magento 2 Admin Email Notifications](https://magefan.com/magento-2-admin-email-notifications) 128 | * [Magento 2 Login As Customer](https://magefan.com/login-as-customer-magento-2-extension) 129 | 130 | ### Magento 2 Blog Extensions 131 | 132 | * [Magento 2 Blog](https://magefan.com/magento2-blog-extension) 133 | * [Magento 2 Multi Blog](https://magefan.com/magento-2-multi-blog-extension) 134 | * [Magento 2 Product Widget](https://magefan.com/magento-2-product-widget) 135 | 136 | ### [Magento 2 Marketing Automation Extensions](https://magefan.com/magento-2-extensions/marketing-automation) 137 | 138 | * [Magento 2 Cookie Consent](https://magefan.com/magento-2-cookie-consent) 139 | * [Magento 2 Product Labels](https://magefan.com/magento-2-product-labels) 140 | * [Magento 2 Base Price](https://magefan.com/magento-2-base-price) 141 | * [Magento 2 Dynamic Categories](https://magefan.com/magento-2-dynamic-categories) 142 | * [Magento 2 Dynamic Blocks and Pages Extension](https://magefan.com/magento-2-cms-display-rules-extension) 143 | * [Magento 2 Automatic Related Products](https://magefan.com/magento-2-automatic-related-products) 144 | * [Magento 2 Price History](https://magefan.com/magento-2-price-history) 145 | * [Magento 2 Mautic Integration](https://magefan.com/magento-2-mautic-extension) 146 | * [Magento 2 YouTube Video](https://magefan.com/magento2-youtube-extension) 147 | 148 | ### [Magento 2 Cart Extensions](https://magefan.com/magento-2-extensions/cart-extensions) 149 | 150 | * [Magento 2 Checkout Extension](https://magefan.com/better-magento-2-checkout-extension) 151 | * [Magento 2 Coupon Code](https://magefan.com/magento-2-coupon-code-link) 152 | * [Magento 2 Guest to Customer](https://magefan.com/magento2-convert-guest-to-customer) 153 | 154 | ### [Magento 2 Multi-Language Extensions](https://magefan.com/magento-2-extensions/multi-language-extensions) 155 | 156 | * [Magento 2 Hreflang Tags](https://magefan.com/magento2-alternate-hreflang-extension) 157 | * [Magento 2 Auto Currency Switcher](https://magefan.com/magento-2-currency-switcher-auto-currency-by-country) 158 | * [Magento 2 Auto Language Switcher](https://magefan.com/magento-2-auto-language-switcher) 159 | * [Magento 2 GeoIP Store Switcher](https://magefan.com/magento-2-geoip-switcher-extension) 160 | * [Magento 2 Translation](https://magefan.com/magento-2-translation-extension) 161 | 162 | ### [Developers Tools](https://magefan.com/magento-2-extensions/developer-tools) 163 | 164 | * [Magento 2 Zero Downtime Deployment](https://magefan.com/blog/magento-2-zero-downtime-deployment) 165 | * [Magento 2 Cron Schedule](https://magefan.com/magento-2-cron-schedule) 166 | * [Magento 2 CLI Extension](https://magefan.com/magento2-cli-extension) 167 | * [Magento 2 Conflict Detector](https://magefan.com/magento2-conflict-detector) 168 | 169 | ## [Shopify Apps](https://magefan.com/shopify/apps) by Magefan 170 | 171 | * [Shopify Login As Customer](https://apps.shopify.com/login-as-customer) 172 | * [Shopify Blog](https://apps.shopify.com/magefan-blog) 173 | * [Shopify Size Chart](https://magefan.com/shopify/apps/size-chart) 174 | * [Shopify Google Indexer](https://magefan.com/shopify/apps/google-indexing) 175 | * [Shopify Product Feeds](https://magefan.com/shopify/apps/product-feed) 176 | -------------------------------------------------------------------------------- /Setup/Patch/Data/ConvertConfigToJsonPatch.php: -------------------------------------------------------------------------------- 1 | moduleDataSetup = $moduleDataSetup; 62 | $this->config = $config; 63 | $this->resource = $resource; 64 | $this->serializer = $serializer; 65 | $this->logger = $logger; 66 | } 67 | 68 | /** 69 | * @return void 70 | */ 71 | public function apply() 72 | { 73 | $this->moduleDataSetup->startSetup(); 74 | 75 | $connection = $this->moduleDataSetup->getConnection(); 76 | $tableName = $this->moduleDataSetup->getTable('core_config_data'); 77 | 78 | $query = $connection->select() 79 | ->from($tableName, ['config_id','value']) 80 | ->where( 81 | 'path = ?', 82 | Config::XML_PATH_LAZY_BLOCKS 83 | ) 84 | ->order('config_id ASC'); 85 | 86 | $result = $connection->fetchAll($query); 87 | 88 | foreach ($result as $scope) { 89 | if (!isset($scope['config_id']) || !isset($scope['value'])) { 90 | continue; 91 | } 92 | 93 | $blocks = $this->getBlocks($scope['value']); 94 | if (empty($blocks)) { 95 | continue; 96 | } 97 | 98 | $jsonBlocks = $this->getJsonForBlocks($blocks); 99 | 100 | try { 101 | $connection->update( 102 | $tableName, 103 | ['value' => $jsonBlocks], 104 | [ 105 | 'config_id = ?' => $scope['config_id'] 106 | ] 107 | ); 108 | } catch (\Exception $e) { 109 | $this->logger->debug(__('Magefan LazyLoad ERROR: while converting to json for config_id: ') . $scope['config_id']); 110 | continue; 111 | } 112 | } 113 | 114 | $this->moduleDataSetup->endSetup(); 115 | } 116 | 117 | /** 118 | * @param $block 119 | * @return string 120 | */ 121 | protected function getNumberHashForBlock ($block): string { 122 | $numberHashFromString = sprintf('%u', crc32($block)); 123 | $numberHashFromStringSuffix = substr($numberHashFromString, -3); 124 | 125 | return '_' . $numberHashFromString . $numberHashFromStringSuffix . '_' . $numberHashFromStringSuffix; 126 | } 127 | 128 | /** 129 | * @param $blocks 130 | * @return bool|string 131 | */ 132 | protected function getJsonForBlocks($blocks) 133 | { 134 | $arrayBlocks = []; 135 | foreach ($blocks as $block) { 136 | $arrayBlocks[$this->getNumberHashForBlock($block)] = 137 | [ 138 | 'block_identifier' => $block, 139 | 'first_images_to_skip' => ($block == 'category.products.list') ? '2' : '0' 140 | ]; 141 | } 142 | 143 | return $this->serializer->serialize($arrayBlocks); 144 | } 145 | 146 | /** 147 | * @return array|string[] 148 | */ 149 | public static function getDependencies() 150 | { 151 | return []; 152 | } 153 | 154 | /** 155 | * @return array|string[] 156 | */ 157 | public function getAliases() 158 | { 159 | return []; 160 | } 161 | 162 | /** 163 | * @param $blocks 164 | * @return array 165 | */ 166 | protected function getBlocks($blocks): array 167 | { 168 | json_decode($blocks); 169 | if (json_last_error() === JSON_ERROR_NONE) { 170 | return []; 171 | } 172 | 173 | $blocks = str_replace(["\r\n", "\n\r", "\n"], "\r", $blocks); 174 | $blocks = explode("\r", $blocks); 175 | $result = []; 176 | foreach ($blocks as $block) { 177 | if ($block = trim($block)) { 178 | $result[] = $block; 179 | } 180 | } 181 | 182 | return $result; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magefan/module-lazyload", 3 | "description": "Images lazyloading on Magento 2 store.", 4 | "require": { 5 | "magefan/module-community" : ">=2.2.2" 6 | }, 7 | "suggest": { 8 | "magefan/module-webp": "Install WebP Optimized Images to serve images in the next-gen format. Use coupon code COMPOSER-FAN to get 10% discount on magefan.com.", 9 | "magefan/module-rocketjavascript": "Install Rocket JavaScript to optimize JS loading." 10 | }, 11 | "type": "magento2-module", 12 | "version": "2.1.6", 13 | "authors": [ 14 | { 15 | "name": "Magefan", 16 | "email": "support@magefan.com" 17 | } 18 | ], 19 | "autoload": { 20 | "files": [ 21 | "registration.php" 22 | ], 23 | "psr-4": { 24 | "Magefan\\LazyLoad\\": "" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /etc/acl.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 |
13 | 14 | magefan 15 | Magefan_LazyLoad::config_magefan_lazyload 16 | 17 | 18 | 1 19 | 20 | Magefan\LazyLoad\Block\Adminhtml\System\Config\Form\Info 21 | 22 | 23 | 24 | Magento\Config\Model\Config\Source\Yesno 25 | 26 | 27 | 28 | Magefan\Community\Block\Adminhtml\System\Config\Form\ProductKeyField 29 | 30 | 31 | 32 | 33 | Magefan\LazyLoad\Model\Config\Source\Method 34 | 35 | 36 | 37 | 38 | Magefan\LazyLoad\Model\Config\Source\BlocksToLazyLoad 39 | 40 | If the "All" option is chosen:
42 | Lazy Load will be added to all blocks. In the "Lazy Load Blocks" section you can skip a few images for some blocks to make them visible immediately. 43 |
44 | If the "Selected" option is chosen:
45 | You need to specify the blocks in which images should be lazy-loaded. Here you can also skip the first few images. 46 | ]]>
47 |
48 | 49 | 50 | 51 | Magefan\LazyLoad\Block\Adminhtml\System\Config\Form\DynamicRow 52 | Magento\Config\Model\Config\Backend\Serialized\ArraySerialized 53 | 55 | Example:
56 | block.name.in.layout
57 | \Vendor\ModuleName\Block\SomeBlock\Interceptor
58 | Vendor_ModuleName::folder/mytemplate.phtml
59 | folder/sometemplate.phtml 60 | ]]>
61 |
62 | 63 | 64 | 65 | 0 66 | 67 | The noscript HTML element defines a section of HTML that is inserted if a script type on the page is unsupported or if scripting is currently turned off in the browser. This option enables the display of images even when JavaScript is disabled in the browser and lazy load js script cannot be loaded. Note that when enabled it adds extra HTML tags to the page. 68 | Magento\Config\Model\Config\Source\Yesno 69 | 70 |
71 |
72 |
73 |
74 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | LazyLoad 15 | 1 16 | 0 17 | 1 18 | 19 | { 20 | "_1686830676971_971":{"block_identifier":"footer","first_images_to_skip":"0"}, 21 | "_1686830692627_627":{"block_identifier":"category.products.list","first_images_to_skip":"2"}, 22 | "_1686830698763_763":{"block_identifier":"search_result_list","first_images_to_skip":"0"}, 23 | "_1686830706539_539":{"block_identifier":"catalog.product.related","first_images_to_skip":"0"}, 24 | "_1686830709731_731":{"block_identifier":"product.info.upsell","first_images_to_skip":"0"}, 25 | "_1686830724821_821":{"block_identifier":"product.info.details","first_images_to_skip":"0"}, 26 | "_1686830727491_491":{"block_identifier":"checkout.cart.crosssell","first_images_to_skip":"0"}, 27 | "_1686830759755_755":{"block_identifier":"blog.post","first_images_to_skip":"0"}, 28 | "_1686830764027_27":{"block_identifier":"blog.posts.list","first_images_to_skip":"0"}, 29 | "_1686830775499_499":{"block_identifier":"blog.sidebar","first_images_to_skip":"0"}, 30 | "_1686830777092_92":{"block_identifier":"blog.post.relatedproducts","first_images_to_skip":"0"}, 31 | "_1686830777637_637":{"block_identifier":"product.info.description","first_images_to_skip":""} 32 | } 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | mfdislazy 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /etc/frontend/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | Magento\Catalog\Block\Product\Image\Interceptor 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /etc/frontend/events.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /view/adminhtml/ui_component/pagebuilder_image_form.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 |
11 |
12 | 13 | 14 | 15 | boolean 16 | Exlude From Lazy Load 17 | checkbox 18 | toggle 19 | exlude_from_lazy_load 20 | 21 | 1 22 | 0 23 | 24 | 0 25 | 26 | 27 | 28 |
29 |
30 | -------------------------------------------------------------------------------- /view/frontend/layout/default.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | img,div 17 | originalset 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /view/frontend/layout/mflazyzoad_no_js.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /view/frontend/templates/lazy.phtml: -------------------------------------------------------------------------------- 1 | 9 | 15 | isNoScriptEnabled()) { ?> 16 | 22 | 23 | getIsJavascriptLazyLoadMethod()) { ?> 24 | 100 || isMfLazyPixelImageInAViewPort(document.querySelector('main img[src$=\"pixel.jpg\"], main .mflazy-background-image, div[data-original]'))) { 28 | loadLazyLoad(); 29 | } 30 | }, 10); 31 | 32 | document.addEventListener('DOMContentLoaded', function () { 33 | if (isMfLazyPixelImageInAViewPort(document.querySelector('main img[src$=\"pixel.jpg\"], main .mflazy-background-image, div[data-original]'))) { 34 | loadLazyLoad(); 35 | } else { 36 | document.addEventListener('scroll', loadLazyLoad, { once: true }); 37 | document.addEventListener('click', loadLazyLoad, { once: true }); 38 | } 39 | }); 40 | 41 | function isMfLazyPixelImageInAViewPort(element, offset = 100) 42 | { 43 | return element && ((element.getBoundingClientRect().top - offset) < window.innerHeight); 44 | } 45 | 46 | function loadLazyLoad() { 47 | clearInterval(window.mfLazyUtilLoad); 48 | " . ($block->isNoScriptEnabled() ? " 49 | document.body.className = document.body.className.replace('mflazyzoad-no-js', ''); 50 | " : "") . " 51 | var jsSrc = '{$block->getViewFileUrl('Magefan_LazyLoad::js/lazyload.min.js')}'; 52 | function loadScript(e,t){var a,n,r;n=!1,(a=document.createElement('script')).type='text/javascript',a.src=e,a.onload=a.onreadystatechange=function(){n||this.readyState&&\"complete\"!=this.readyState||(n=!0,t())},(r=document.getElementsByTagName('script')[0]).parentNode.insertBefore(a,r)} 53 | loadScript(jsSrc, function(){ 54 | var lazyLoadConfig = {$block->getLazyLoadConfig()}; 55 | var myLazyLoad = false; 56 | if (document.readyState !== 'loading') { 57 | myLazyLoad = new LazyLoad(lazyLoadConfig); 58 | setTimeout(function(){ 59 | new LazyLoad(lazyLoadConfig); 60 | }, 2000); 61 | } else { 62 | document.addEventListener('DOMContentLoaded', function() { 63 | myLazyLoad = new LazyLoad(lazyLoadConfig); 64 | setTimeout(function(){ 65 | new LazyLoad(lazyLoadConfig); 66 | }, 2000); 67 | }); 68 | } 69 | 70 | document.body.addEventListener('contentUpdated', function(){ 71 | if (myLazyLoad) { 72 | myLazyLoad.update(); 73 | } 74 | }); 75 | return true; 76 | }); 77 | } 78 | "; 79 | ?> 80 | renderTag('script', ['data-rocketjavascript' => 'false'], $script, false) ?> 81 | 167 | renderTag('script', [], $script, false) ?> 168 | 169 | 170 | 173 | -------------------------------------------------------------------------------- /view/frontend/web/images/pixel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magefan/module-lazyload/f0d4a1a2890ed89fecd034fbc569ada7763e1d42/view/frontend/web/images/pixel.jpg -------------------------------------------------------------------------------- /view/frontend/web/js/lazyload.min.js: -------------------------------------------------------------------------------- 1 | /* Origin https://github.com/verlok/vanilla-lazyload/blob/8.0.0/dist/lazyload.min.js */ 2 | var _extends=Object.assign||function(a){for(var b=1;b=d(a)+c+a.offsetHeight},i=function(a,b,c){return(b===window?window.pageXOffset:f(b))>=f(a)+c+a.offsetWidth},j=function(a,b,c){return!(e(a,b,c)||h(a,b,c)||g(a,b,c)||i(a,b,c))},k=function(a,b){var c=new a(b),d=new CustomEvent("LazyLoad::Initialized",{detail:{instance:c}});window.dispatchEvent(d)},l=function(a,b){var c=a.parentElement;if("PICTURE"===c.tagName)for(var d=0;d0;)d.splice(g.pop(),1),c(a.callback_processed,d.length);0===e&&this._stopScrollHandler(),h&&(this._isFirstLoop=!1)},_purgeElements:function(){var a=this._elements,b=a.length,c=void 0,d=[];for(c=0;c0;)a.splice(d.pop(),1)},_startScrollHandler:function(){this._isHandlingScroll||(this._isHandlingScroll=!0,this._settings.container.addEventListener("scroll",this._boundHandleScroll))},_stopScrollHandler:function(){this._isHandlingScroll&&(this._isHandlingScroll=!1,this._settings.container.removeEventListener("scroll",this._boundHandleScroll))},handleScroll:function(){var a=this,b=this._settings.throttle;0!==b?function(){var c=function(){(new Date).getTime()},d=c(),e=b-(d-a._previousLoopTime);e<=0||e>b?(a._loopTimeout&&(clearTimeout(a._loopTimeout),a._loopTimeout=null),a._previousLoopTime=d,a._loopThroughElements()):a._loopTimeout||(a._loopTimeout=setTimeout(function(){this._previousLoopTime=c(),this._loopTimeout=null,this._loopThroughElements()}.bind(a),e))}():this._loopThroughElements()},update:function(){this._elements=Array.prototype.slice.call(this._queryOriginNode.querySelectorAll(this._settings.elements_selector)),this._purgeElements(),this._loopThroughElements(),this._startScrollHandler()},destroy:function(){window.removeEventListener("resize",this._boundHandleScroll),this._loopTimeout&&(clearTimeout(this._loopTimeout),this._loopTimeout=null),this._stopScrollHandler(),this._elements=null,this._queryOriginNode=null,this._settings=null}};var o=window.lazyLoadOptions;return o&&function(a,b){var c=b.length;if(c)for(var d=0;d