Vue图片懒加载:极简方案 vs 兼容全攻略

大家好,我是小杨,一个在摸爬滚打中做了6年前端的老兵。今天咱们聊聊图片懒加载这个老话题,但我会给你两种截然不同的实现方案------一种是追求极致简洁的现代方案,另一种则是兼容老浏览器的稳妥方案。

一、现代浏览器极简方案(IntersectionObserver API)

如果你不需要考虑IE等老浏览器,这个方案简洁到让你感动:

javascript 复制代码
// 懒加载指令
const lazyLoad = {
  mounted(el, binding) {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          el.src = binding.value
          observer.unobserve(el)
        }
      })
    })
    observer.observe(el)
  }
}

// 全局注册
app.directive('lazy', lazyLoad)

// 使用方式
<img v-lazy="'https://example.com/my-photo.jpg'" alt="我的照片">

优点

  • 代码不到20行
  • 性能极佳(浏览器原生支持)
  • 自动处理滚动事件
  • 可配置阈值(threshold)和根元素(root)

进阶用法:添加加载占位和错误处理

javascript

ini 复制代码
<img 
  v-lazy="'https://example.com/my-photo.jpg'"
  src="/placeholder.jpg"
  @error="handleImageError"
  alt="我的旅行照片"
>

二、兼容性方案(传统滚动检测+data-src)

如果需要兼容IE11等老浏览器,我们就得回到传统方案:

javascript 复制代码
// 工具函数:判断元素是否在视口内
function isInViewport(el) {
  const rect = el.getBoundingClientRect()
  return (
    rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
    rect.bottom >= 0 &&
    rect.left <= (window.innerWidth || document.documentElement.clientWidth) &&
    rect.right >= 0
  )
}

// 懒加载指令
const lazyLoadCompat = {
  mounted(el, binding) {
    el.setAttribute('data-src', binding.value)
    
    const loadImage = () => {
      if (isInViewport(el)) {
        el.src = el.dataset.src
        el.removeAttribute('data-src')
        window.removeEventListener('scroll', scrollHandler)
      }
    }
    
    const scrollHandler = throttle(loadImage, 200)
    
    loadImage() // 初始检查
    window.addEventListener('scroll', scrollHandler)
    
    // 组件卸载时清理
    el._scrollHandler = scrollHandler
  },
  unmounted(el) {
    window.removeEventListener('scroll', el._scrollHandler)
  }
}

// 使用方式
<img v-lazy-compat="'https://example.com/my-old-photo.jpg'" alt="我的老照片">

关键改进点

  1. 添加了节流函数(throttle)优化性能
  2. 组件卸载时移除事件监听
  3. 初始加载时立即检查一次
  4. 使用data-src存储真实URL

配套的节流函数实现

javascript 复制代码
function throttle(fn, delay) {
  let lastCall = 0
  return function(...args) {
    const now = new Date().getTime()
    if (now - lastCall < delay) return
    lastCall = now
    return fn.apply(this, args)
  }
}

三、两种方案性能对比

我在Chrome 89上做了简单测试(100张图片):

方案 首次加载时间 滚动流畅度 内存占用
IntersectionObserver 120ms 60fps 15MB
传统方案 350ms 45fps 22MB

四、生产环境建议

  1. 现代方案增强版
javascript 复制代码
const observer = new IntersectionObserver(callback, {
  rootMargin: '200px 0px' // 提前200px加载
})
  1. 传统方案优化点
  • 添加图片加载失败的重试机制
  • 使用requestAnimationFrame优化滚动检测
  • 对离屏图片进行卸载处理
  1. 混合方案(推荐):
javascript 复制代码
// 检测浏览器支持情况
const lazyLoad = window.IntersectionObserver 
  ? lazyLoadModern 
  : lazyLoadCompat

五、写在最后

技术选型就像谈恋爱------没有最好的,只有最合适的。如果你的用户都在用最新浏览器,大胆使用IntersectionObserver;如果需要照顾老用户,传统方案也很可靠。

小杨的私房建议:现在项目基本上都是Vue3了,不妨试试这个组合拳:

javascript 复制代码
// 组合式API版本
import { onMounted, onUnmounted } from 'vue'

export function useLazyLoad() {
  let observer
  
  const initObserver = (el, src) => {
    observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          entry.target.src = src
          observer.unobserve(entry.target)
        }
      })
    })
    observer.observe(el)
  }
  
  onUnmounted(() => {
    observer?.disconnect()
  })
  
  return { initObserver }
}

希望这篇分享能帮你少走弯路!如果有更好的实现方案,欢迎在评论区交流~

⭐ 写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

相关推荐
brzhang26 分钟前
OpenAI 7周发布Codex,我们的数据库迁移为何要花一年?
前端·后端·架构
军军君0144 分钟前
基于Springboot+UniApp+Ai实现模拟面试小工具三:后端项目基础框架搭建上
前端·vue.js·spring boot·面试·elementui·微信小程序·uni-app
布丁052344 分钟前
DOM编程实例(不重要,可忽略)
前端·javascript·html
bigyoung1 小时前
babel 自定义plugin中,如何判断一个ast中是否是jsx文件
前端·javascript·babel
指尖的记忆1 小时前
当代前端人的 “生存技能树”:从切图仔到全栈侠的魔幻升级
前端·程序员
草履虫建模1 小时前
Ajax原理、用法与经典代码实例
java·前端·javascript·ajax·intellij-idea
轻语呢喃2 小时前
useReducer : hook 中的响应式状态管理
javascript·后端·react.js
时寒的笔记2 小时前
js入门01
开发语言·前端·javascript
陈随易2 小时前
MoonBit能给前端开发带来什么好处和实际案例演示
前端·后端·程序员
996幸存者2 小时前
uniapp图片上传组件封装,支持添加、压缩、上传(同时上传、顺序上传)、预览、删除
前端