1<?php 2/** 3 * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved. 4 * 5 * This source code is licensed under the GPL license found in the 6 * COPYING file in the root directory of this source tree. 7 * 8 * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 9 * @author ComboStrap <support@combostrap.com> 10 * 11 */ 12 13namespace ComboStrap; 14 15use ComboStrap\TagAttribute\BackgroundAttribute; 16use ComboStrap\TagAttribute\StyleAttribute; 17 18/** 19 * This one support background loading 20 * https://github.com/ApoorvSaxena/lozad.js 21 * 22 * 23 * TODO: implement no script pattern ? the https://github.com/aFarkas/lazysizes#the-noscript-pattern 24 * 25 */ 26class LazyLoad 27{ 28 29 const CONF_LAZY_LOADING_PLACEHOLDER_COLOR = "lazyLoadingPlaceholderColor"; 30 31 /** 32 * Lozad was choosen because 33 * it was easier to add svg injection 34 * it supports background image 35 * it's most used (JsDelivr stats) 36 */ 37 const ACTIVE = self::LOZAD_ID; 38 39 /** 40 * The id of the lazy loaders 41 */ 42 const LAZY_SIDE_ID = "lazy-sizes"; 43 const LOZAD_ID = "lozad"; 44 45 46 const CANONICAL = "lazy"; 47 const DEFAULT_COLOR = "#cbf1ea"; 48 49 public const LAZY_LOAD_METHOD_HTML_VALUE = "html"; 50 public const LAZY_LOAD_METHOD_LOZAD_VALUE = "lozad"; 51 52 /** 53 * The method on how to lazy load resources (Ie media) 54 */ 55 public const LAZY_LOAD_METHOD = "lazy"; 56 /** 57 * The default when the image are above the fold 58 */ 59 public const LAZY_LOAD_METHOD_NONE_VALUE = "none"; 60 /** 61 * Used internal for now on test 62 */ 63 const CONF_LAZY_LOAD_METHOD = "internal-lazy-load-method-combo"; 64 public const CONF_RASTER_ENABLE = "rasterImageLazyLoadingEnable"; 65 public const CONF_RASTER_ENABLE_DEFAULT = 1; 66 public const HTML_LOADING_ATTRIBUTE = "loading"; 67 68 /** 69 * Used to select all lazy loaded 70 * resources and load them before print 71 */ 72 public static function getLazyClass(): string 73 { 74 return StyleAttribute::addComboStrapSuffix(self::CANONICAL); 75 } 76 77 public static function addSnippet() 78 { 79 switch (self::ACTIVE) { 80 case self::LAZY_SIDE_ID: 81 LazyLoad::addLazySizesSnippet(); 82 break; 83 case self::LOZAD_ID: 84 LazyLoad::addLozadSnippet(); 85 break; 86 default: 87 throw new \RuntimeException("The active lazy loaded is unknown (" . self::ACTIVE . ")"); 88 } 89 90 } 91 92 /** 93 * Add the lazy sizes snippet 94 */ 95 private static function addLazySizesSnippet() 96 { 97 98 $snippetManager = PluginUtility::getSnippetManager(); 99 100 $snippetManager->attachRemoteJavascriptLibrary( 101 self::LAZY_SIDE_ID, 102 "https://cdn.jsdelivr.net/npm/lazysizes@5.3.1/lazysizes.min.js", 103 "sha256-bmG+LzdKASJRACVXiUC69++Nu8rz7MX1U1z8gb0c/Tk=" 104 ); 105 /** 106 * The Spinner effect 107 * lazysizes adds the class lazy loading while the images are loading 108 * and the class lazyloaded as soon as the image is loaded. 109 */ 110 $snippetManager->attachCssInternalStyleSheet(self::LAZY_SIDE_ID); 111 112 } 113 114 /** 115 * @param TagAttributes $attributes 116 */ 117 public static function addPlaceholderBackground(&$attributes) 118 { 119 // https://github.com/ApoorvSaxena/lozad.js#large-image-improvment 120 $placeholderColor = LazyLoad::getPlaceholderColor(); 121 if ($attributes->hasComponentAttribute(BackgroundAttribute::BACKGROUND_COLOR)) { 122 $placeholderColor = $attributes->getValueAndRemove(BackgroundAttribute::BACKGROUND_COLOR); 123 } 124 $attributes->addOutputAttributeValue("data-placeholder-background", "$placeholderColor"); 125 126 127 } 128 129 /** 130 * Add lozad 131 * Support background image 132 * https://github.com/ApoorvSaxena/lozad.js 133 */ 134 public static function addLozadSnippet() 135 { 136 137 $snippetManager = PluginUtility::getSnippetManager(); 138 139 // https://www.jsdelivr.com/package/npm/lozad 140 $snippetManager 141 ->attachRemoteJavascriptLibrary( 142 self::LOZAD_ID, 143 "https://cdn.jsdelivr.net/npm/lozad@1.16.0/dist/lozad.min.js", 144 "sha256-mOFREFhqmHeQbXpK2lp4nA3qooVgACfh88fpJftLBbc=" 145 ) 146 ->setDoesManipulateTheDomOnRun(false); 147 148 /** 149 * Add the fading effect 150 */ 151 $snippetId = "lazy-load-fade"; 152 $snippetManager->attachCssInternalStyleSheet($snippetId); 153 154 155 /** 156 * Snippet to download the image before print 157 * 158 * The others javascript snippet to download lazy load depend on the image type 159 * and features and was therefore added in the code for svg or raster 160 */ 161 $snippetManager->attachJavascriptFromComponentId("lozad-print"); 162 163 164 } 165 166 /** 167 * Class selector to identify the element to lazy load 168 */ 169 public static function getClass() 170 { 171 switch (self::ACTIVE) { 172 case self::LAZY_SIDE_ID: 173 return "lazyload"; 174 case self::LOZAD_ID: 175 return "lozad"; 176 default: 177 throw new \RuntimeException("The active lazy loaded is unknown (" . self::ACTIVE . ")"); 178 } 179 } 180 181 /** 182 * @return string - the lazy loading placeholder color 183 */ 184 public static function getPlaceholderColor() 185 { 186 return SiteConfig::getConfValue(self::CONF_LAZY_LOADING_PLACEHOLDER_COLOR, self::DEFAULT_COLOR); 187 } 188 189 /** 190 * The placeholder is not mandatory 191 * but if present, it should have the same target ratio of the image 192 * 193 * This function is documenting this fact. 194 * 195 * @param null $imgTagWidth 196 * @param null $imgTagHeight 197 * @return string 198 * 199 * 200 * Src is always set, this is the default 201 * src attribute is served to browsers that do not take the srcset attribute into account. 202 * When lazy loading, we set the srcset to a transparent image to not download the image in the src 203 * 204 */ 205 public static function getPlaceholder($imgTagWidth = null, $imgTagHeight = null): string 206 { 207 if ($imgTagWidth != null) { 208 $svg = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 $imgTagWidth $imgTagHeight'></svg>"; 209 /** 210 * We encode it to be able to 211 * use it in a `srcset` attribute that does not 212 * want any space in the image definition 213 */ 214 $svgBase64 = base64_encode($svg); 215 $image = "data:image/svg+xml;base64,$svgBase64"; 216 } else { 217 /** 218 * Base64 transparent gif 219 * 1x1 image, it will produce a square 220 */ 221 $image = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; 222 } 223 return $image; 224 } 225 226 /** 227 * @return void 228 * @deprecated use {@link SiteConfig::disableLazyLoad()} 229 */ 230 public static function disable() 231 { 232 ExecutionContext::getActualOrCreateFromEnv() 233 ->getConfig() 234 ->disableLazyLoad(); 235 } 236 237 /** 238 * 239 * By default, the image above the fold should not be lazy loaded 240 * Above-the-fold images that are lazily loaded render later in the page lifecycle, which can delay the largest contentful paint. 241 * 242 * 243 */ 244 public static function getDefault() 245 { 246 247 try { 248 /** 249 * Above-the-fold images that are lazily loaded render later in the page lifecycle, 250 * which can delay the largest contentful paint. 251 */ 252 $sourcePath = ExecutionContext::getActualOrCreateFromEnv() 253 ->getExecutingMarkupHandler() 254 ->getSourcePath(); 255 if(SlotSystem::isMainHeaderSlot($sourcePath)){ 256 return LazyLoad::LAZY_LOAD_METHOD_NONE_VALUE; 257 } 258 } catch (ExceptionNotFound $e) { 259 // not a path execution 260 } 261 262 /** 263 * HTML and not lozad as default because in a Hbs template, in a {@link TemplateForWebPage}, 264 * it would not work as the script would not be added 265 */ 266 return ExecutionContext::getActualOrCreateFromEnv() 267 ->getConfig() 268 ->getValue(LazyLoad::CONF_LAZY_LOAD_METHOD,LazyLoad::LAZY_LOAD_METHOD_HTML_VALUE) ; 269 } 270 271} 272