Vue 指令相关文章:
数据接口准备:
- 新建一个后端工程 server, 用Node.js express
- 数据准备:
json
[
{
"id": 1,
"title": "Web 开发技术",
"img" : "http://localhost:3000/images/1.webp"
},
{
"id": 2,
"title": "深入浅出 Vue.js",
"img" : "http://localhost:3000/images/2.webp"
},
{
"id": 3,
"title": "JavaScript 高级程序设计 第四版",
"img" : "http://localhost:3000/images/3.webp"
},
{
"id": 4,
"title": "JavaScript + jQuery",
"img" : "http://localhost:3000/images/4.webp"
},
{
"id": 5,
"title": "javascript 前端开发 程序设计 ",
"img" : "http://localhost:3000/images/5.webp"
},
{
"id": 6,
"title": "Web 前端开发技术 HTML CSSJ JAVSCRIPT",
"img" : "http://localhost:3000/images/6.webp"
},
{
"id": 7,
"title": "Web 前端开发精品课 HTML 与CSS进阶",
"img" : "http://localhost:3000/images/7.webp"
},
{
"id": 8,
"title": "Web 前端开发实战教程",
"img" : "http://localhost:3000/images/8.webp"
},
{
"id": 9,
"title": "Web 前端开发技术",
"img" : "http://localhost:3000/images/9.webp"
},
{
"id": 10,
"title": "Web 前端技术",
"img" : "http://localhost:3000/images/10.webp"
},
{
"id": 11,
"title": "零基础 html + css 从入门到精通",
"img" : "http://localhost:3000/images/11.webp"
},
{
"id": 12,
"title": "javascript 高级程序设计第四版",
"img" : "http://localhost:3000/images/12.webp"
},
{
"id": 13,
"title": "Javascript 入门经典",
"img" : "http://localhost:3000/images/13.webp"
},
{
"id": 14,
"title": "Web前端开发技术",
"img" : "http://localhost:3000/images/14.webp"
},
{
"id": 15,
"title": "WebAssembly 原理与核心技术",
"img" : "http://localhost:3000/images/15.webp"
},
{
"id": 16,
"title": "互联网前端技术开发",
"img" : "http://localhost:3000/images/16.webp"
}
]
接口编写:
js
const express = require('express')
const { resolve } = require('path')
const { readFileSync } = require('fs')
const app = express()
app.all('*', (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-methods', 'POST,GET')
next()
})
app.get('/images/:filename', (req, res) => {
res.sendFile(resolve(__dirname, './images/' + req.params.filename))
})
app.get('/book-list', (req, res) => {
const imageData = readFileSync(resolve(__dirname, './data/imageData.json'), 'utf8')
console.log(imageData)
res.send(JSON.parse(imageData))
})
app.listen(3000, () => {
console.log('http://localhost:3000')
})
开源项目的 v-lazy 的使用
1 新建前端工程 vue-lazy 2. 安装axios , vue-lazyload 3. main.js 引用,并使用use 使用
js
import { createApp } from 'vue'
import App from './App.vue'
import VueLazyload from 'vue-lazyload'
createApp(App).use(VueLazyload, {
preLoad: 1.3,
loading: './assets/images/loading.gif',
error: './images/images/error.webp'
}).mount('#app')
- 组件内使用
js
<script setup>
import { onMounted, ref } from 'vue'
import axios from 'axios'
const imgList = ref([])
onMounted(async () => {
const data = await axios('http://localhost:3000/book-list')
imgList.value = data.data
})
</script>
<template>
<div class="container">
<ul class="img-list">
<li v-for="item in imgList" :key="item.id">
<img v-lazy="item.img" />
<p>{{ item.title }}</p>
</li>
</ul>
</div>
</template>
<style scoped>
html,
body,
ul,
p {
margin: 0;
padding: 0;
}
.container {
height: 100%;
overflow: auto;
}
.img-list li {
list-style: none;
display: flex;
height: 140px;
margin-bottom: 30px;
border-bottom: 1px solid #ddd;
padding-bottom: 20px;
}
.img-list li img {
width: 120px;
margin-right: 20px;
}
.img-list li p {
flex: 1
}
</style>
观察效果:
可以看到控制台之只有5张图片的请求,但是我们的接口是有16条数据的
这就实现了图片的懒加载。
v-lazy 实现
分析v-lazy 做了那些事情:
- 只在用户设定的preLoad 区域内加载图片
- 滚动的时候还需要判断图片是否在用户设定的区域
- 图片有没有被加载过,已经加载过了,再次滚动的时候不再重新执行加载逻辑
- 图片还没加载完成时会先加载loading 图片
- 加载出错时会显示一个加载错误的图片
针对第一点: 怎么计算出图片位置是否在设定的区域内呢?
我们可以用getBoundingClientRect
获取到距离顶部的距离和用户传进来的preLoad 设定的区域进行对比
针对第二、三点需要做两件事:
(1)根据图片位置查找到带有overflow 属性值为auto 或者scroll的元素
(2)监听查找到的带有overflow 属性值为auto 或者为scroll的元素的scroll事件
(3)给每个图片实例都添加一个loaded 属性, 没加载过标记为false, 加载过了标记为true
针对四、五两点: (1)根据图片的onload 和onerror 回调来判断是现实真实图片还是error 图片
代码结构设计:
- 新建modules文件夹
- modules 文件夹下新建lazyload
- lazyload 文件夹下新建index.js 作为vue-lazyload 的出口文件
- 新建Lazyload.js 文件主要用来处理滚动相关逻辑和渲染逻辑
- 新建LazyImg.js 用来创建图片实例的类,保存相关状态,状态改变时,调用渲染函数渲染对应状态的图片
- 新建utils.js 放置一些工具函数, 比如查找overflow 属性值为auto 或者为scroll的元素
来看下具体代码:
index.js
js
import Lazyload from './Lazyload'
const VueLazyload = {
install (app, options) {
const LazyClass = Lazyload(app)
const lazyLoad = new LazyClass(options)
app.directive('lazy' ,{
mounted: lazyLoad.bindLazy.bind(lazyLoad)
})
}
}
export default VueLazyload
LazyImg.js
js
import { imgLoad } from './utils'
export default class LazyImg {
constructor ({el, src, options, imageRender}) {
this.el = el
this.src = src
this.options = options
this.imageRender = imageRender
this.loaded = false
this.state = {
error: false,
loading: false
}
}
checkVisible () {
const { top } = this.el.getBoundingClientRect()
return top < window.innerHeight * (this.options.preLoad || 1.3)
}
loadImg () {
this.imageRender(this, 'loading')
this.loading = true
imgLoad(this.src).then(res => {
this.loading = false
this.loaded = true
this.imageRender(this, 'ok')
}).catch(error => {
this.loading = false
this.error = true
this.loaded = true
this.imageRender(this, 'error')
})
}
}
Lazyload.js
js
import { getParentNode } from "./utils"
import LazyImg from './LazyImg'
function Lazyload (app) {
return class Lazy {
constructor (options) {
this.options = options
this.isAddEvnt = false
this.imgPool = []
}
bindLazy (el, binding, vnode) {
const scrollParent = getParentNode(el)
if (scrollParent && !this.isAddEvnt) {
this.isAddEvnt = true
scrollParent.addEventListener('scroll', this.handleScroll.bind(this), false)
}
const lazyImg = new LazyImg({
el,
src: binding.value,
options: this.options,
imageRender: this.imageRender.bind(this)
})
console.log(lazyImg)
this.imgPool.push(lazyImg)
this.handleScroll()
}
imageRender (lazy, state) {
const { el, options } = lazy
const { loading, error } = options
let src = ''
switch (state) {
case 'loading':
src = loading
break
case 'error':
src = error
break
default:
src = lazy.src
}
el.setAttribute('src', src)
}
handleScroll () {
this.imgPool.forEach(item => {
if (!item.loaded) {
const isVisible = item.checkVisible()
isVisible && item.loadImg()
}
})
}
}
}
export default Lazyload
utils.js
js
export function getParentNode (el) {
var _parent = el.parentNode
while(_parent) {
const _styleOverflow = getComputedStyle(_parent).overflow
if (/(scroll)|(auto)/.test(_styleOverflow)) {
return _parent
}
_parent = _parent.parentNode
}
}
export function imgLoad (src) {
return new Promise((resolve, reject) => {
const oImg = new Image()
oImg.src = src
oImg.onload = resolve()
oImg.onerror = reject()
})
}
将main.js 种vue-lazyload的引用改为我们自己写的vue-lazyload
js
import { createApp } from 'vue'
import App from './App.vue'
import VueLazyload from './modules/vue-lazyload'
createApp(App).use(VueLazyload, {
preLoad: 1.3,
loading: './assets/images/loading.webp',
error: './images/images/error.webp'
}).mount('#app')
来看看效果:
可以看到,我们也实现了和开源项目一样的效果,第一屏只加载了5张图片。
我们再往下滚动观察效果:
又多加载了两张图片, 符合预期。
到此Vue 自定义实现v-lazy 就分享到这里了,感谢收看,一起学习一起进步。
Vue 指令相关文章: