Keen Slider场景下:如何延迟图片懒加载至进入视口一定比例?
解决方案:自定义懒加载控制加载时机,确保过渡动画可见
针对图片加载过快无法展示过渡动画、iOS轮播图片跳闪的问题,可以通过自定义Intersection Observer懒加载逻辑精准控制主图加载时机,同时保证过渡动画正常展示。
核心思路
- 放弃浏览器原生
loading="lazy"的自动加载逻辑,改用Intersection Observer监听图片容器的可见性 - 设置触发加载的阈值(比如图片50%进入视口时才开始加载主图)
- 主图初始不设置
src,而是存在data-src属性中,进入视口后再赋值加载 - 可选:添加最小延迟确保动画必现,避免图片加载过快跳过动画
修改步骤
1. 更新HTML结构
将主图的src替换为data-src,阻止浏览器自动加载主图:
<!-- 修改前 --> <img src="https://placebeard.it/768/1024" width="768" height="1024" loading="lazy" /> <!-- 修改后 --> <img data-src="https://placebeard.it/768/1024" width="768" height="1024" />
2. 替换懒加载JavaScript代码
移除原有图片加载检测逻辑,改用Intersection Observer实现自定义懒加载:
/* ========================================================================== #CUSTOM LAZY LOAD WITH INTERSECTION OBSERVER ========================================================================== */ // 配置Observer:当元素50%进入视口时触发加载 const observerConfig = { root: null, // 基于视口检测 rootMargin: '0px', threshold: 0.5 // 可见比例达到50%时触发 }; const imageObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const pixelContainer = entry.target; const mainImg = pixelContainer.querySelector('img[data-src]'); if (!mainImg) return; // 开始加载主图 mainImg.src = mainImg.dataset.src; mainImg.removeAttribute('data-src'); function triggerAnimation() { // 可选:添加最小延迟,确保动画必现(哪怕图片瞬间加载完成) setTimeout(() => { pixelContainer.classList.add('loaded'); mainImg.removeEventListener('load', triggerAnimation); observer.unobserve(pixelContainer); // 停止监听已处理的元素 }, 300); } // 处理图片已缓存的情况 if (mainImg.complete) { triggerAnimation(); } else { mainImg.addEventListener('load', triggerAnimation); } } }); }, observerConfig); // 监听所有图片容器 document.querySelectorAll('.pixel-load').forEach(container => { imageObserver.observe(container); });
3. 保留原有Keen Slider代码
Keen Slider的逻辑无需修改,Intersection Observer会自动处理轮播滑动时的元素可见性变化。
关键说明
- 加载时机控制:通过
threshold: 0.5设置只有当图片容器50%进入视口才开始加载主图,避免提前加载导致动画被跳过 - iOS跳闪解决:占位图始终显示直到主图加载完成并触发动画,不会出现空白闪烁
- 动画必现优化:
setTimeout的300ms延迟保证即使图片加载极快,用户也能看到完整的过渡动画,可根据需求调整时长 - 性能优化:加载完成后调用
unobserve停止监听已处理的元素,减少不必要的性能消耗
完整代码示例
修改后的HTML
<!-- Keen Slider --> <div class="gallery"> <div id="gallery-slider" class="keen-slider"> <div class="keen-slider__slide"> <figure data-label="Hover Label 1" class="has-label"> <div class="pixel-load"> <div class="pixel-load__preload"> <img src="https://placebeard.it/18/24" width="18" height="24" loading="eager"> </div> <img data-src="https://placebeard.it/768/1024" width="768" height="1024" /> </div> <figcaption>Slide 1</figcaption> </figure> </div> <div class="keen-slider__slide"> <figure data-label="Hover Label 2" class="has-label"> <div class="pixel-load"> <div class="pixel-load__preload"> <img src="https://placebeard.it/18/24" width="18" height="24" loading="eager"> </div> <img data-src="https://placebeard.it/768/1024" width="768" height="1024" /> </div> <figcaption>Slide 2</figcaption> </figure> </div> <div class="keen-slider__slide"> <figure data-label="Hover Label 3" class="has-label"> <div class="pixel-load"> <div class="pixel-load__preload"> <img src="https://placebeard.it/18/24" width="18" height="24" loading="eager"> </div> <img data-src="https://placebeard.it/768/1024" width="768" height="1024" /> </div> <figcaption>Slide 3</figcaption> </figure> </div> <div class="keen-slider__slide"> <figure data-label="Hover Label 4" class="has-label"> <div class="pixel-load"> <div class="pixel-load__preload"> <img src="https://placebeard.it/18/24" width="18" height="24" loading="eager"> </div> <img data-src="https://placebeard.it/768/1024" width="768" height="1024" /> </div> <figcaption>Slide 4</figcaption> </figure> </div> <div class="keen-slider__slide"> <figure data-label="Hover Label 5" class="has-label"> <div class="pixel-load"> <div class="pixel-load__preload"> <img src="https://placebeard.it/18/24" width="18" height="24" loading="eager"> </div> <img data-src="https://placebeard.it/768/1024" width="768" height="1024" /> </div> <figcaption>Slide 5</figcaption> </figure> </div> <div class="keen-slider__slide"> <figure data-label="Hover Label 6" class="has-label"> <div class="pixel-load"> <div class="pixel-load__preload"> <img src="https://placebeard.it/18/24" width="18" height="24" loading="eager"> </div> <img data-src="https://placebeard.it/768/1024" width="768" height="1024" /> </div> <figcaption>Slide 6</figcaption> </figure> </div> <div class="keen-slider__slide"> <figure data-label="Hover Label 7" class="has-label"> <div class="pixel-load"> <div class="pixel-load__preload"> <img src="https://placebeard.it/18/24" width="18" height="24" loading="eager"> </div> <img data-src="https://placebeard.it/768/1024" width="768" height="1024" /> </div> <figcaption>Slide 7</figcaption> </figure> </div> <div class="keen-slider__slide"> <figure data-label="Hover Label 8" class="has-label"> <div class="pixel-load"> <div class="pixel-load__preload"> <img src="https://placebeard.it/18/24" width="18" height="24" loading="eager"> </div> <img data-src="https://placebeard.it/768/1024" width="768" height="1024" /> </div> <figcaption>Slide 8</figcaption> </figure> </div> </div> </div> <!-- End Keen Slider --> <script src="https://cdn.jsdelivr.net/npm/keen-slider@6.8.5/keen-slider.min.js"></script>
修改后的JavaScript
/* ========================================================================== #CUSTOM LAZY LOAD WITH INTERSECTION OBSERVER ========================================================================== */ const observerConfig = { root: null, rootMargin: '0px', threshold: 0.5 }; const imageObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const pixelContainer = entry.target; const mainImg = pixelContainer.querySelector('img[data-src]'); if (!mainImg) return; mainImg.src = mainImg.dataset.src; mainImg.removeAttribute('data-src'); function triggerAnimation() { setTimeout(() => { pixelContainer.classList.add('loaded'); mainImg.removeEventListener('load', triggerAnimation); observer.unobserve(pixelContainer); }, 300); } if (mainImg.complete) { triggerAnimation(); } else { mainImg.addEventListener('load', triggerAnimation); } } }); }, observerConfig); document.querySelectorAll('.pixel-load').forEach(container => { imageObserver.observe(container); }); /* ========================================================================== #KEEN SLIDER ========================================================================== */ var animation = { duration: 32000, easing: (t) => t } new KeenSlider("#gallery-slider", { dragSpeed: 1, loop: true, mode: "free", slides: { perView: 1.5, renderMode: "performance", spacing: 8 }, breakpoints: { '(min-width: 768px)': { slides: { perView: 3, spacing: 8 } }, '(min-width: 1024px)': { slides: { perView: 4, spacing: 8 } } }, created(s) { document.querySelector("#gallery-slider").classList.add("loaded"); s.moveToIdx(5, true, animation); }, updated(s) { s.moveToIdx(s.track.details.abs + 5, true, animation); }, animationEnded(s) { s.moveToIdx(s.track.details.abs + 5, true, animation); } })
CSS代码无需修改,保持原有的过渡动画逻辑即可。
内容的提问来源于stack exchange,提问作者user1406440
相关产品推荐
相关产品推荐

