关于图片加载,你需要学习一下

news/2024/12/2 20:40:11/

目录

😊 从何而来

🥕 设计思路

🍭 初步设计

🚩 成功和失败

🚧 加载失败

🐬 回调函数

⌛    懒加载

🚥 对外接口

🚩 测试使用

🏆 完整代码


😊 从何而来

        这篇文章,出自我自己的开源组件库 fighting-design[2] 中的 Avatar 头像[3] 组件的 load-image[4]类。

        相比于其它的静态组件,像图片加载这种的组件,内部我做了很多的优化,对于图片的加载和错误的处理,我都尽可能的将每种可能出现的结果都考虑到,针对每种不确定的结果做出相应的提示,以便于提升用户体验。

🥕 设计思路

        我的设计想法是:通过一个加载类,传入 dom 元素、 props 和 emit。先创建出一个虚拟的 image 元素进行尝试加载,加载成功获失败都会进入下一步的函数,做出对应从处理逻辑。

🍭 初步设计

首先类中先有一个加载的方法 loadCreateImg,代码如下:

class Load {constructor(node, props, emit) {this.node = nodethis.props = propsthis.emit = emit}// 加载 srcloadCreateImg = () => {const newImg = new Image() // 新建一个虚拟的 imgnewImg.src = this.props.src // 将传入的 src 赋值给虚拟节点// src 加载失败newImg.addEventListener('error', (evt) => {// 加载失败的处理})// src 加载成功newImg.addEventListener('load', (evt) => {// 加载成功的处理})}
}

        首先我创建了一个 Load 的加载类,需要传入 node 参数作为最终需要渲染的 dom 节点,props 是传入的组件内部的 props 参数,内部包含图片需要加载的 src 路径,emit 包括一些回调参数。

        类的内部有个 loadCreateImg 的方法,调用可创建一个虚拟的 Image 元素,直接将传入的 props.src 赋值并加载。监听上面的 error 和 load 事件,即可监听到图片是否加载成功,以便做出不同的状态。

🚩 成功和失败

        对于成功或失败的处理,我新增了 onerror 和 onload 方法,来处理加载成功和失败之后的不同处理状态

class Load {constructor(node, props, emit) {this.node = nodethis.props = propsthis.emit = emit}loadCreateImg = () => {const newImg = new Image()newImg.src = this.props.srcnewImg.addEventListener('error', (evt) => {this.onerror(evt) // 新增})newImg.addEventListener('load', (evt) => {this.onload(evt) // 新增})}// 加载成功onload = (evt) => {this.node.src = this.props.src}// 加载失败onerror = (evt) => {// ……}
}

        对于加载成功,处理方式是,将传入的真实的 dom 节点直接赋值为传入的 props.src 即可完成加载。

🚧 加载失败

        对于加载失败的处理,Fighting Design 内部做了很多处理,比如可以传入 err-src 的备用路径加载,在 src 加载失败之后,如果 err-src 存在的话,那么就需要加载 err-src 。接下来继续完善类方法:

        首先要在 onerror 方法中判断是否存在 err-src,如果有 err-src 那么就需要重新调用 loadCreateImg 重新加载,但是现在的代码显然不能满足需要,所以 loadCreateImg 需要接收一个可选的参数为 errSrc,因为只有在加载失败之后才需要再次调用该方法传入 err-src,所以方法内部就可以根据 err-src 是否存在,来做出不同的处理:

class Load {constructor(node, props, emit) {this.node = nodethis.props = propsthis.emit = emit}loadCreateImg = (errSrc?: string) => {const newImg = new Image()// 如果 errSrc 存在 就尝试加载 errSrcif (errSrc) {newImg.src = errSrc} else {newImg.src = this.props.src}newImg.addEventListener('error', (evt) => {this.onerror(evt)})newImg.addEventListener('load', (evt) => {this.onload(evt)})}onload = (evt) => {this.node.src = this.props.src}// 加载失败onerror = (evt) => {// 如果存在 errSrc 则继续尝试加载if (this.props.errSrc) {// 将 errSrc 传给 loadCreateImg 方法return this.loadCreateImg(this.props.errSrc)}// 否则返回失败回调this.emit('error', evt)}
}

但是上面代码存在两个问题:

1. 首先我们发现,在 `onload` 加载成功的方法中,将真实 `dom` 赋值的始终 是 `src`:

onload = (evt) => {// 始终赋值为 props.srcthis.node.src = this.props.src
}
复制代码

        但是 src 并不是始终可以加载成功的,所以还是需要动态的去将真正加载成功的 src 传给 onload 方法,那么真正加载成功的 src 也就是在 load 方法中。并且还要加入成功的 emit

2. 其次,在处理加载失败的 `onerror` 方法中,因为判断了如果存在 `errSrc` 就继续调用 `loadCreateImg` 加载方法重新加载。问题是,如果传入了 `errSrc` 那么 `if (this.props.errSrc)` 其实是始终为真的,这也就导致了死循环,会重复调用加载函数。 

onerror = (evt) => {// 判断始终为真if (this.props.errSrc) {return this.loadCreateImg(this.props.errSrc)}// 否则返回失败回调this.emit('error', evt)
}
复制代码

        所以就需要给它一个可以变为假的时机,那么修复方法为:在传给 loadCreateImg 方法之后,将 errSrc 清空,这样加载一次之后就可以判断为假了,所以完整代码为:

class Load {constructor(node, props, emit) {this.node = nodethis.props = propsthis.emit = emit}loadCreateImg = (errSrc?: string) => {const newImg = new Image()// 如果 errSrc 存在 就尝试加载 errSrcif (errSrc) {newImg.src = errSrc} else {newImg.src = this.props.src}newImg.addEventListener('error', (evt) => {this.onerror(evt)})newImg.addEventListener('load', (evt) => {this.onload(evt, newImg.src) // 将加载成功的 src 传给 onload 函数})}// 新增 src 属性onload = (evt, src: string) => {this.node.src = src // 将真实 dom 的 src 赋值给传入的 srcthis.emit('load', evt) // 新增}onerror = (evt) => {if (this.props.errSrc) {this.loadCreateImg(this.props.errSrc)this.props.errSrc = '' // 清空 errSrc 避免重复调用死循环return}this.emit('error', evt)}
}

🐬 回调函数

有些时候,我们还需要通过一个布尔值来判断图片是否加载成功,或者进行其它判断。

    Fighting Design 内部对图片加载失败做了特殊的样式处理来提示用户,所以需要一个布尔值和 v-if 来展示不同的状态,这里就涉及到了类的第四个参数,也就是一个可选的回调函数

        这样就可以在加载成功和加载失败的时候通过回调函数来返回一个布尔值判断是否加载成功,代码如下:

class Load {constructor(node, props, emit, callback) {this.node = nodethis.props = propsthis.emit = emitthis.callback = callback // 新增 callback 参数}loadCreateImg = (errSrc?: string) => {const newImg = new Image()if (errSrc) {newImg.src = errSrc} else {newImg.src = this.props.src}newImg.addEventListener('error', (evt) => {this.onerror(evt)})newImg.addEventListener('load', (evt) => {this.onload(evt, newImg.src)})}onload = (evt, src: string) => {this.node.src = srcthis.emit('load', evt)// 如果 callback 存在,在加载成功的时候返回 trueif (this.callback) {this.callback(true)}}onerror = (evt) => {if (this.props.errSrc) {this.loadCreateImg(this.props.errSrc)this.props.errSrc = ''return}this.emit('error', evt)// 如果 callback 存在,在加载失败的时候返回 falseif (this.callback) {this.callback(false)}}
}

上面代码即可实现判断是否加载成功的需求。

当然,回调函数你可以尽情的发挥想象做出更多的事情,这里仅提供部分用法。

⌛ 懒加载

        图片的懒加载,也是一个图片加载必备的功能了,这里我使用的是内置的 IntersectionObserver[5]接口,对于这个方法,这里不过多描述,各位可以通过 MDN[6] 进行学习。

        对于懒加载,因为这是一个可选的属性,并不是每次都需要,所以我将懒加载单独抽离出来的一个 Lazy 类进行实现,再将 Lazy 类继承到 Load 类,代码如下:

class Lazy extends Load {constructor(img, props, emit, callback) {// super 关键字调用super(img, props, emit, callback)}observer = () => {const observer = new IntersectionObserver((arr): void => {// 如果进入可视区域if (arr[0].isIntersecting) {// 开始加载图片 调用父类this.loadCreateImg()observer.unobserve(this.node)}},/*** rootMargin 为触发懒加载的距离 通过 props 传入* https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin*/{ rootMargin: this.props.rootMargin })return observer}// 执行 懒加载lazyCreateImg = (): void => {// IntersectionObserver 内部方法,需要将 dom 节点传入this.observer().observe(this.node)}
}

    IntersectionObserver 接口可以判断 dom 元素是否进入可视区域,通过内置方法判断进入可视区域之后,执行父类的 loadCreateImg 方法进行加载,从而实现懒加载。

🚥 对外接口

        对于 Load 类和 Lazy 类,Fighting Design 并没有直接暴露出去提供使用,而是暴露出了一个全新的 loadImage 函数,让它去根据是否为懒加载而实例化不同的类,再调用加载方法:

// 导出对外接口
export const loadImage = (node, prop, emit, callback) => {/*** 如果传入了 lazy 则执行懒加载类* 否则执行正常加载类*/if (prop.lazy) {const lazy = new Lazy(node, prop, emit, callback)return lazy.lazyCreateImg()}const load = new Load(node, prop, emit, callback)load.loadCreateImg()
}

🚩 测试使用

写好的函数测试一下看看:

<script lang="ts" setup>import { loadImage } from '../../packages/fighting-design/_utils'import { ref, onMounted } from 'vue'const myImg = ref(null as unknown as HTMLImageElement)// 模拟 propsconst props = {src: 'https://tianyuha2o.cn/images/auto/my.jpg',errSrc: 'https://tianyuhao.cn/images/auto/4.jpg',lazy: true}onMounted(() => {loadImage(myImg.value, props)})
</script><template><img ref="myImg" src="" />
</template>

可以看到,是成功执行的。

🏆 完整代码

        完整代码可参考 load-image[7]


http://www.ppmy.cn/news/78589.html

相关文章

如何成为一名黑客?小白必学的12个基本步骤

黑客攻防是一个极具魅力的技术领域&#xff0c;但成为一名黑客毫无疑问也并不容易。你必须拥有对新技术的好奇心和积极的学习态度&#xff0c;具备很深的计算机系统、编程语言和操作系统知识&#xff0c;并乐意不断地去学习和进步。 如果你想成为一名优秀的黑客&#xff0c;下…

微信小程序【发送给朋友】和【复制链接】功能,灰色不可用

每日鸡汤&#xff1a;悲观者可能正确&#xff0c;但是乐观者往往成功 假设你是一个用户&#xff0c;你随便找一个小程序可以看到这几个功能 转发给朋友分享到朋友圈复制链接 很常见的功能&#xff0c;但是如果你作为开发者&#xff0c;这几个功能就需要自己做喽&#xff0c;并…

跨境电商的三大平台Amazon、eBay、速卖通,你怎么选择?

很多刚刚开始决定做跨境电商的朋友&#xff0c;第一个难点就是&#xff1a;我要从哪个平台开始做呢&#xff1f;在没有正式做过之前&#xff0c;很难去了解到每个平台的不同&#xff0c;但是也不可能同时去运营太多个平台。考虑到这个难处&#xff0c;龙哥今天就从亚马逊、ebay…

如何利用CiteSpace快速锁定领域内最新研究热点并制作精美的可视化专题图

在科研工作中&#xff0c;我们常常需要面对海量的文献进行阅读和分析&#xff0c;如何在这些文献当中找出值得精读、细读的关键文献&#xff0c;挖掘学科前沿&#xff0c;找到研究热点就成为了开展研究之前首先需要解决的问题。CiteSpace作为一款优秀的文献计量学软件&#xff…

QT5.15.0使用gcc-arm-8.2-2018.08-x86_64-arm-linux-gnueabihf交叉编译的问题总结

目录 一、交叉编译 二、操作中踩过的坑 1、环境变量未生效 2、交叉编译QT代码操作 3、烧录时报错缺少xcb问题 4、小白的细小错误 三、--platform命令 3、1 -platform linuxfb 详细文档请点击此处 我的文档在原文档的基础上添加了非常详细的提醒&#xff0c;可以少走弯路…

请问你如何理解以下的歌词“unravel - TK from 凛冽时雨 (TK from 凛として時雨)为什么很多人说崖山海战以后无中国

目录 请问你如何理解以下的歌词“unravel - TK from 凛冽时雨 (TK from 凛として時雨) 为什么很多人说崖山海战以后无中国 请问你如何理解以下的歌词“unravel - TK from 凛冽时雨 (TK from 凛として時雨) 以下是我对《unravel - TK from 凛冽时雨》这首歌词的理解&#xff1…

Selenium + Java 的环境搭建

Selenium Java 的环境搭建 &#x1f50e;Chrome 浏览器下载 Chrome 浏览器检查对应版本下载 Chrome 浏览器驱动 &#x1f50e;配置环境变量&#x1f50e;验证环境是否搭建成功&#x1f50e;关于 pom.xml 出现错误的解决方案 &#x1f50e;Chrome 浏览器 下载 Chrome 浏览器 下…

光敏晶体管(ALS-PT19-315C/L177/TR8) 光照度和电压,电流关系分析.

背景 当我们使用光敏晶体管进行,测算光照度时,大多使用ADC电路测到电压. 那么怎么根据这个电压计算出对应具体的光照度呢? 下面将以 ALS-PT19-315C/L177/TR8 型号的 光敏晶体管为例,来进行分析介绍,并给出 如何根据最大光照度范围 选定合适的电阻和电容. 1,直接看数据手册给…