You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Keen Slider场景下:如何延迟图片懒加载至进入视口一定比例?

解决方案:自定义懒加载控制加载时机,确保过渡动画可见

针对图片加载过快无法展示过渡动画、iOS轮播图片跳闪的问题,可以通过自定义Intersection Observer懒加载逻辑精准控制主图加载时机,同时保证过渡动画正常展示。

核心思路

  1. 放弃浏览器原生loading="lazy"的自动加载逻辑,改用Intersection Observer监听图片容器的可见性
  2. 设置触发加载的阈值(比如图片50%进入视口时才开始加载主图)
  3. 主图初始不设置src,而是存在data-src属性中,进入视口后再赋值加载
  4. 可选:添加最小延迟确保动画必现,避免图片加载过快跳过动画

修改步骤

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 12:40:55