1/** 2 * Unveil Intersection 3 * 4 * This is a lightweight module which would 5 * lazy load images. And, dynamically resize our images. 6 * If, it contains the `dynamic-resize-(background)` class. 7 * 8 * After doing any changes in this file remember to minify it by running 9 * `npm run build` in this directory. 10 */ 11 12(() => { 13 'use strict'; 14 15 const domainRegex = /(.*\/\/.*)?(pictures|images|static)(\.)([^.]+\.)?dealer\.com(.*)/; 16 const IMG = "image_tag"; 17 const BG_IMG = "background_image"; 18 19 class ImageElement { 20 constructor(img) { 21 this.img = img; 22 this.wrappedImage = img.getElementsByTagName('img')[0]; 23 this.source = img.getAttribute("data-src"); 24 this.type = img.getAttribute("data-src") ? IMG : BG_IMG; 25 if(this.type !== IMG) { 26 // In case the image is a background or a linked image 27 if (img.classList.contains('cta-background') || img.nodeName === 'A') { 28 this.wrapperDiv = img; 29 } else { 30 // In case of sections, make sure that the given wrapped div 31 // has a data-responsive-image-bg attribute 32 const sectionWrapper = !img.hasAttribute('data-responsive-image-bg') && img.closest("[data-responsive-image-group]"); 33 this.wrapperDiv = sectionWrapper ? sectionWrapper.querySelector('[data-responsive-image-bg]') : img; 34 } 35 } 36 if(!this.source) { 37 this.source = img.getAttribute("data-background-image"); 38 } 39 // Support ws-content-cta widget 40 if(!this.source) { 41 // If the image has srcset 42 if(this.wrappedImage?.hasAttribute("data-sizes")) { 43 this.wrappedImage.setAttribute("sizes", this.wrappedImage.getAttribute("data-sizes")); 44 } 45 if(this.wrappedImage?.hasAttribute("data-srcset")) { 46 this.wrappedImage.setAttribute("srcset", this.wrappedImage.getAttribute("data-srcset")); 47 this.source = this.wrappedImage?.currentSrc; 48 } 49 50 // If the image has no srcset 51 if(this.wrappedImage?.hasAttribute("data-src")) { 52 this.source = this.wrappedImage.getAttribute("data-src"); 53 } 54 55 } 56 if(!this.source) { 57 if(img.hasAttribute("data-style")) { 58 img.setAttribute("style", img.getAttribute("data-style")) 59 } 60 this.source = ''; 61 } 62 } 63 64 setSource(newSource) { 65 this.source = newSource; 66 } 67 68 getSource() { 69 return this.source; 70 } 71 72 setLazyImage() { 73 const imgUrl = this.getSource(); 74 if(imgUrl) { 75 if(this.type === IMG) { 76 this.img.setAttribute("src", imgUrl); 77 } else { 78 // if lazyloading is already completed, skip setting it again 79 const isAlreadyLoaded = this.wrapperDiv?.hasAttribute('data-lazy-initialized'); 80 if(!isAlreadyLoaded) { 81 if (this.wrapperDiv?.getAttribute("style")) { 82 // Incase the backgroundImage style already has gradient set. 83 const existingUrl = this.wrapperDiv.style['backgroundImage'] 84 ? this.wrapperDiv.style['backgroundImage'].match(/url\(\s*(?<url>.*?)\s*\)/)?.[0] ?? "" 85 : ""; 86 this.wrapperDiv.style['backgroundImage'] = existingUrl 87 ? this.wrapperDiv.style['backgroundImage'].replace(existingUrl, `url("${imgUrl}")`) 88 : `url("${imgUrl}")`; 89 } else { 90 this.wrapperDiv?.setAttribute('style', `background-image: url("${imgUrl}")`); 91 } 92 // set a flag indicating that lazyloading has finished 93 this.wrapperDiv?.setAttribute('data-lazy-initialized', true) 94 } 95 } 96 } 97 return this; 98 } 99 100 cleanLazyLoadAttrs() { 101 this.wrappedImage?.classList?.remove('lazy-image'); 102 this.wrapperDiv?.classList?.remove('lazy-background-image'); 103 this.wrapperDiv?.removeAttribute('data-background-image'); 104 this.img.classList.remove('lazy-image'); 105 this.img.classList.remove('lazy-background-image'); 106 this.img.classList.remove('lazy-element'); 107 this.img.removeAttribute('data-background-image'); 108 this.img.removeAttribute('data-sizes'); 109 this.img.removeAttribute('data-src'); 110 this.img.removeAttribute('data-srcset'); 111 this.img.removeAttribute('data-style'); 112 if(this.img.getAttribute('width') === '1') { 113 this.img.removeAttribute('width'); 114 } 115 if(this.img.getAttribute('height') === '1') { 116 this.img.removeAttribute('height'); 117 } 118 } 119 120 isDealerImage() {
121 return this.getSource().indexOf('/static/') === 0 || domainRegex.test(this.getSource()); 122 } 123 124 addParam(name, value) { 125 const imgUrl = this.getSource(); 126 // Check if params don't already exists 127 if (imgUrl.indexOf(`?${name}=`) === -1 && imgUrl.indexOf(`&${name}=`) === -1) { 128 const param = `${name}=${value}`; 129 imgUrl.indexOf("?") === -1 130 ? this.setSource(`${imgUrl}?${param}`) 131 : this.setSource(`${imgUrl}&${param}`); 132 } 133 return this; 134 } 135 136 addResizerParamsToSource(width) { 137 this.addParam('impolicy', 'downsize_bkpt').addParam('w', Math.round(width).toString()); 138 } 139 140 dynamicallyResizeImage() { 141 const target = this.img; 142 const widthMultiplier = parseFloat(target.getAttribute('data-width-multiplier') || this.wrappedImage?.getAttribute('data-width-multiplier') || 1); 143 144 if (!this.isDealerImage()) { 145 return this; 146 } 147 148 // Fix static.dealer.com paths to be /static/ while we're already modifying things. 149 if (this.getSource().indexOf('static.dealer.com') > -1) { 150 this.setSource(this.getSource().replace(/^(http(s?)\:)?\/\/static\.dealer\.com\//i, '/static/')); 151 } 152 153 if(target.classList.contains('dynamic-resize') || (this.wrappedImage?.classList?.contains('dynamic-resize') || false)) { 154 const minImageWidth = target.getAttribute('min-width'); 155 let parentsWidth = target?.closest('li')?.getBoundingClientRect()?.width 156 || target?.closest('dd')?.getBoundingClientRect()?.width 157 || target?.closest('div')?.getBoundingClientRect()?.width; 158 159 parentsWidth = minImageWidth && parentsWidth < minImageWidth ? minImageWidth : parentsWidth; 160 161 if (parentsWidth > 0) { 162 this.addResizerParamsToSource(parentsWidth * widthMultiplier); 163 } 164 } else if(target.classList.contains('dynamic-resize-background')) { 165 const width = target?.getBoundingClientRect()?.width ?? 0; 166 if (width > 0) { 167 this.addResizerParamsToSource(width * widthMultiplier); 168 } 169 } 170 return this; 171 } 172 173 lazyLoad() { 174 this.dynamicallyResizeImage().setLazyImage().cleanLazyLoadAttrs(); 175 } 176 } 177 178 const unveil = (selector, context = document, threshold) => { 179 const elements = context.querySelectorAll(selector); 180 const options = { 181 rootMargin: `${threshold}px 0px` 182 } 183 184 const handleUnveil = entries => { 185 entries.forEach(entry => { 186 if (entry.isIntersecting) { 187 const img = entry.target; 188 const imageElement = new ImageElement(img); 189 imageElement.lazyLoad(); 190 191 observer.unobserve(img); 192 } 193 }); 194 } 195 196 const observer = new IntersectionObserver(handleUnveil, options); 197 elements.forEach(el => el.classList.contains("d-none") 198 // To support ws-content-cta widget and section backgrounds which hides the img tag. 199 ? observer.observe(el.closest('div')) 200 : observer.observe(el) 201 ); 202 203 } 204 205 const DDC = (window.DDC = (window.DDC || {})); 206 DDC.unveil = unveil; 207})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.