137748cd8SNickeau<?php 237748cd8SNickeau/** 337748cd8SNickeau * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved. 437748cd8SNickeau * 537748cd8SNickeau * This source code is licensed under the GPL license found in the 637748cd8SNickeau * COPYING file in the root directory of this source tree. 737748cd8SNickeau * 837748cd8SNickeau * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 937748cd8SNickeau * @author ComboStrap <support@combostrap.com> 1037748cd8SNickeau * 1137748cd8SNickeau */ 1237748cd8SNickeau 1337748cd8SNickeaunamespace ComboStrap; 1437748cd8SNickeau 15*04fd306cSNickeauuse ComboStrap\TagAttribute\BackgroundAttribute; 16*04fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute; 17*04fd306cSNickeau 1837748cd8SNickeau/** 1937748cd8SNickeau * This one support background loading 2037748cd8SNickeau * https://github.com/ApoorvSaxena/lozad.js 2137748cd8SNickeau * 2237748cd8SNickeau * 2337748cd8SNickeau * TODO: implement no script pattern ? the https://github.com/aFarkas/lazysizes#the-noscript-pattern 2437748cd8SNickeau * 2537748cd8SNickeau */ 2637748cd8SNickeauclass LazyLoad 2737748cd8SNickeau{ 2837748cd8SNickeau 2937748cd8SNickeau const CONF_LAZY_LOADING_PLACEHOLDER_COLOR = "lazyLoadingPlaceholderColor"; 3037748cd8SNickeau 3137748cd8SNickeau /** 3237748cd8SNickeau * Lozad was choosen because 3337748cd8SNickeau * it was easier to add svg injection 3437748cd8SNickeau * it supports background image 3537748cd8SNickeau * it's most used (JsDelivr stats) 3637748cd8SNickeau */ 3737748cd8SNickeau const ACTIVE = self::LOZAD_ID; 3837748cd8SNickeau 3937748cd8SNickeau /** 4037748cd8SNickeau * The id of the lazy loaders 4137748cd8SNickeau */ 4237748cd8SNickeau const LAZY_SIDE_ID = "lazy-sizes"; 4337748cd8SNickeau const LOZAD_ID = "lozad"; 4437748cd8SNickeau 4537748cd8SNickeau 4637748cd8SNickeau const CANONICAL = "lazy"; 4737748cd8SNickeau const DEFAULT_COLOR = "#cbf1ea"; 4837748cd8SNickeau 49*04fd306cSNickeau public const LAZY_LOAD_METHOD_HTML_VALUE = "html"; 50*04fd306cSNickeau public const LAZY_LOAD_METHOD_LOZAD_VALUE = "lozad"; 51*04fd306cSNickeau 52*04fd306cSNickeau /** 53*04fd306cSNickeau * The method on how to lazy load resources (Ie media) 54*04fd306cSNickeau */ 55*04fd306cSNickeau public const LAZY_LOAD_METHOD = "lazy"; 56*04fd306cSNickeau /** 57*04fd306cSNickeau * The default when the image are above the fold 58*04fd306cSNickeau */ 59*04fd306cSNickeau public const LAZY_LOAD_METHOD_NONE_VALUE = "none"; 60*04fd306cSNickeau /** 61*04fd306cSNickeau * Used internal for now on test 62*04fd306cSNickeau */ 63*04fd306cSNickeau const CONF_LAZY_LOAD_METHOD = "internal-lazy-load-method-combo"; 64*04fd306cSNickeau public const CONF_RASTER_ENABLE = "rasterImageLazyLoadingEnable"; 65*04fd306cSNickeau public const CONF_RASTER_ENABLE_DEFAULT = 1; 66*04fd306cSNickeau public const HTML_LOADING_ATTRIBUTE = "loading"; 67*04fd306cSNickeau 6837748cd8SNickeau /** 6937748cd8SNickeau * Used to select all lazy loaded 7037748cd8SNickeau * resources and load them before print 7137748cd8SNickeau */ 72*04fd306cSNickeau public static function getLazyClass(): string 73*04fd306cSNickeau { 74*04fd306cSNickeau return StyleAttribute::addComboStrapSuffix(self::CANONICAL); 75*04fd306cSNickeau } 7637748cd8SNickeau 7737748cd8SNickeau public static function addSnippet() 7837748cd8SNickeau { 7937748cd8SNickeau switch (self::ACTIVE) { 8037748cd8SNickeau case self::LAZY_SIDE_ID: 8137748cd8SNickeau LazyLoad::addLazySizesSnippet(); 8237748cd8SNickeau break; 8337748cd8SNickeau case self::LOZAD_ID: 8437748cd8SNickeau LazyLoad::addLozadSnippet(); 8537748cd8SNickeau break; 8637748cd8SNickeau default: 8737748cd8SNickeau throw new \RuntimeException("The active lazy loaded is unknown (" . self::ACTIVE . ")"); 8837748cd8SNickeau } 8937748cd8SNickeau 9037748cd8SNickeau } 9137748cd8SNickeau 9237748cd8SNickeau /** 9337748cd8SNickeau * Add the lazy sizes snippet 9437748cd8SNickeau */ 9537748cd8SNickeau private static function addLazySizesSnippet() 9637748cd8SNickeau { 9737748cd8SNickeau 9837748cd8SNickeau $snippetManager = PluginUtility::getSnippetManager(); 9937748cd8SNickeau 100*04fd306cSNickeau $snippetManager->attachRemoteJavascriptLibrary( 1014cadd4f8SNickeau self::LAZY_SIDE_ID, 1024cadd4f8SNickeau "https://cdn.jsdelivr.net/npm/lazysizes@5.3.1/lazysizes.min.js", 1034cadd4f8SNickeau "sha256-bmG+LzdKASJRACVXiUC69++Nu8rz7MX1U1z8gb0c/Tk=" 10437748cd8SNickeau ); 10537748cd8SNickeau /** 10637748cd8SNickeau * The Spinner effect 10737748cd8SNickeau * lazysizes adds the class lazy loading while the images are loading 10837748cd8SNickeau * and the class lazyloaded as soon as the image is loaded. 10937748cd8SNickeau */ 110*04fd306cSNickeau $snippetManager->attachCssInternalStyleSheet(self::LAZY_SIDE_ID); 11137748cd8SNickeau 11237748cd8SNickeau } 11337748cd8SNickeau 11437748cd8SNickeau /** 11537748cd8SNickeau * @param TagAttributes $attributes 11637748cd8SNickeau */ 11737748cd8SNickeau public static function addPlaceholderBackground(&$attributes) 11837748cd8SNickeau { 11937748cd8SNickeau // https://github.com/ApoorvSaxena/lozad.js#large-image-improvment 12037748cd8SNickeau $placeholderColor = LazyLoad::getPlaceholderColor(); 121*04fd306cSNickeau if ($attributes->hasComponentAttribute(BackgroundAttribute::BACKGROUND_COLOR)) { 122*04fd306cSNickeau $placeholderColor = $attributes->getValueAndRemove(BackgroundAttribute::BACKGROUND_COLOR); 12337748cd8SNickeau } 1244cadd4f8SNickeau $attributes->addOutputAttributeValue("data-placeholder-background", "$placeholderColor"); 12537748cd8SNickeau 12637748cd8SNickeau 12737748cd8SNickeau } 12837748cd8SNickeau 12937748cd8SNickeau /** 13037748cd8SNickeau * Add lozad 13137748cd8SNickeau * Support background image 13237748cd8SNickeau * https://github.com/ApoorvSaxena/lozad.js 13337748cd8SNickeau */ 13437748cd8SNickeau public static function addLozadSnippet() 13537748cd8SNickeau { 13637748cd8SNickeau 13737748cd8SNickeau $snippetManager = PluginUtility::getSnippetManager(); 13837748cd8SNickeau 13937748cd8SNickeau // https://www.jsdelivr.com/package/npm/lozad 1404cadd4f8SNickeau $snippetManager 141*04fd306cSNickeau ->attachRemoteJavascriptLibrary( 1424cadd4f8SNickeau self::LOZAD_ID, 1434cadd4f8SNickeau "https://cdn.jsdelivr.net/npm/lozad@1.16.0/dist/lozad.min.js", 1444cadd4f8SNickeau "sha256-mOFREFhqmHeQbXpK2lp4nA3qooVgACfh88fpJftLBbc=" 14537748cd8SNickeau ) 1464cadd4f8SNickeau ->setDoesManipulateTheDomOnRun(false); 14737748cd8SNickeau 14837748cd8SNickeau /** 14937748cd8SNickeau * Add the fading effect 15037748cd8SNickeau */ 15137748cd8SNickeau $snippetId = "lazy-load-fade"; 152*04fd306cSNickeau $snippetManager->attachCssInternalStyleSheet($snippetId); 15337748cd8SNickeau 15437748cd8SNickeau 15537748cd8SNickeau /** 15637748cd8SNickeau * Snippet to download the image before print 15737748cd8SNickeau * 15837748cd8SNickeau * The others javascript snippet to download lazy load depend on the image type 15937748cd8SNickeau * and features and was therefore added in the code for svg or raster 16037748cd8SNickeau */ 161*04fd306cSNickeau $snippetManager->attachJavascriptFromComponentId("lozad-print"); 16237748cd8SNickeau 16337748cd8SNickeau 16437748cd8SNickeau } 16537748cd8SNickeau 16637748cd8SNickeau /** 16737748cd8SNickeau * Class selector to identify the element to lazy load 16837748cd8SNickeau */ 16937748cd8SNickeau public static function getClass() 17037748cd8SNickeau { 17137748cd8SNickeau switch (self::ACTIVE) { 17237748cd8SNickeau case self::LAZY_SIDE_ID: 17337748cd8SNickeau return "lazyload"; 17437748cd8SNickeau case self::LOZAD_ID: 17537748cd8SNickeau return "lozad"; 17637748cd8SNickeau default: 17737748cd8SNickeau throw new \RuntimeException("The active lazy loaded is unknown (" . self::ACTIVE . ")"); 17837748cd8SNickeau } 17937748cd8SNickeau } 18037748cd8SNickeau 18137748cd8SNickeau /** 18237748cd8SNickeau * @return string - the lazy loading placeholder color 18337748cd8SNickeau */ 18437748cd8SNickeau public static function getPlaceholderColor() 18537748cd8SNickeau { 186*04fd306cSNickeau return SiteConfig::getConfValue(self::CONF_LAZY_LOADING_PLACEHOLDER_COLOR, self::DEFAULT_COLOR); 18737748cd8SNickeau } 18837748cd8SNickeau 18937748cd8SNickeau /** 19037748cd8SNickeau * The placeholder is not mandatory 1911fa8c418SNickeau * but if present, it should have the same target ratio of the image 19237748cd8SNickeau * 19337748cd8SNickeau * This function is documenting this fact. 19437748cd8SNickeau * 19537748cd8SNickeau * @param null $imgTagWidth 19637748cd8SNickeau * @param null $imgTagHeight 19737748cd8SNickeau * @return string 19837748cd8SNickeau * 19937748cd8SNickeau * 20037748cd8SNickeau * Src is always set, this is the default 20137748cd8SNickeau * src attribute is served to browsers that do not take the srcset attribute into account. 20237748cd8SNickeau * When lazy loading, we set the srcset to a transparent image to not download the image in the src 20337748cd8SNickeau * 20437748cd8SNickeau */ 2051fa8c418SNickeau public static function getPlaceholder($imgTagWidth = null, $imgTagHeight = null): string 20637748cd8SNickeau { 20737748cd8SNickeau if ($imgTagWidth != null) { 20837748cd8SNickeau $svg = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 $imgTagWidth $imgTagHeight'></svg>"; 20937748cd8SNickeau /** 21037748cd8SNickeau * We encode it to be able to 21137748cd8SNickeau * use it in a `srcset` attribute that does not 21237748cd8SNickeau * want any space in the image definition 21337748cd8SNickeau */ 21437748cd8SNickeau $svgBase64 = base64_encode($svg); 21537748cd8SNickeau $image = "data:image/svg+xml;base64,$svgBase64"; 21637748cd8SNickeau } else { 21737748cd8SNickeau /** 21837748cd8SNickeau * Base64 transparent gif 21937748cd8SNickeau * 1x1 image, it will produce a square 22037748cd8SNickeau */ 22137748cd8SNickeau $image = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; 22237748cd8SNickeau } 22337748cd8SNickeau return $image; 22437748cd8SNickeau } 225*04fd306cSNickeau 226*04fd306cSNickeau /** 227*04fd306cSNickeau * @return void 228*04fd306cSNickeau * @deprecated use {@link SiteConfig::disableLazyLoad()} 229*04fd306cSNickeau */ 230*04fd306cSNickeau public static function disable() 231*04fd306cSNickeau { 232*04fd306cSNickeau ExecutionContext::getActualOrCreateFromEnv() 233*04fd306cSNickeau ->getConfig() 234*04fd306cSNickeau ->disableLazyLoad(); 235*04fd306cSNickeau } 236*04fd306cSNickeau 237*04fd306cSNickeau /** 238*04fd306cSNickeau * 239*04fd306cSNickeau * By default, the image above the fold should not be lazy loaded 240*04fd306cSNickeau * Above-the-fold images that are lazily loaded render later in the page lifecycle, which can delay the largest contentful paint. 241*04fd306cSNickeau * 242*04fd306cSNickeau * 243*04fd306cSNickeau */ 244*04fd306cSNickeau public static function getDefault() 245*04fd306cSNickeau { 246*04fd306cSNickeau 247*04fd306cSNickeau try { 248*04fd306cSNickeau /** 249*04fd306cSNickeau * Above-the-fold images that are lazily loaded render later in the page lifecycle, 250*04fd306cSNickeau * which can delay the largest contentful paint. 251*04fd306cSNickeau */ 252*04fd306cSNickeau $sourcePath = ExecutionContext::getActualOrCreateFromEnv() 253*04fd306cSNickeau ->getExecutingMarkupHandler() 254*04fd306cSNickeau ->getSourcePath(); 255*04fd306cSNickeau if(SlotSystem::isMainHeaderSlot($sourcePath)){ 256*04fd306cSNickeau return LazyLoad::LAZY_LOAD_METHOD_NONE_VALUE; 257*04fd306cSNickeau } 258*04fd306cSNickeau } catch (ExceptionNotFound $e) { 259*04fd306cSNickeau // not a path execution 260*04fd306cSNickeau } 261*04fd306cSNickeau 262*04fd306cSNickeau /** 263*04fd306cSNickeau * HTML and not lozad as default because in a Hbs template, in a {@link TemplateForWebPage}, 264*04fd306cSNickeau * it would not work as the script would not be added 265*04fd306cSNickeau */ 266*04fd306cSNickeau return ExecutionContext::getActualOrCreateFromEnv() 267*04fd306cSNickeau ->getConfig() 268*04fd306cSNickeau ->getValue(LazyLoad::CONF_LAZY_LOAD_METHOD,LazyLoad::LAZY_LOAD_METHOD_HTML_VALUE) ; 269*04fd306cSNickeau } 270*04fd306cSNickeau 27137748cd8SNickeau} 272