Skip to content

Commit 6de165a

Browse files
authored
Merge pull request #1 from tolking/dev
- refactor plugin - support IntersectionObserver - support background-image - support srcset
2 parents fb3f5ac + ff542ed commit 6de165a

File tree

7 files changed

+266
-28
lines changed

7 files changed

+266
-28
lines changed

README.md

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,93 @@
11
# vue-lazy-loading
22

3-
> *WIP* a vue plugin to better supporting lazy loading for image and iframe
3+
> *alpha* a vue plugin to better supporting lazy loading for image and iframe
44
5-
**The plugin will preferentially use native image and iframe [lazy-loading](https://caniuse.com/#feat=loading-lazy-attr), if the browser does not support it, it will be implemented through [IntersectionObserver](https://caniuse.com/#feat=intersectionobserver)**
5+
**The plugin will preferentially use native image and iframe [lazy-loading](https://caniuse.com/#feat=loading-lazy-attr), if the browser does not support it, it will be implemented through [IntersectionObserver](https://caniuse.com/#feat=intersectionobserver)**
6+
7+
## Installation
8+
9+
``` sh
10+
yarn add vue-lazy-loading
11+
# or
12+
npm i vue-lazy-loading
13+
```
14+
15+
## Usage
16+
17+
``` js
18+
// Vue 3.X
19+
import { createApp } from 'vue'
20+
import LazyLoading from 'vue-lazy-loading'
21+
22+
createApp(App)
23+
.use(LazyLoading)
24+
.mount('#app')
25+
26+
// Vue 2.X
27+
import Vue from 'vue'
28+
import LazyLoading from 'vue-lazy-loading'
29+
30+
Vue.use(LazyLoading)
31+
```
32+
33+
- **Setting a fixed size is better for browser loading**
34+
35+
``` vue
36+
<template>
37+
<img v-lazy="'img.jpg'" width="536" height="354" />
38+
39+
<img v-lazy="logo" width="100" height="100" />
40+
41+
<iframe v-lazy="'iframe.html'" width="1000" height="500" />
42+
43+
<div v-lazy:bg="logo">background</div>
44+
<!--or v-lazy:background="logo"-->
45+
46+
<img v-lazy="'img.jpg'" v-lazy:set="'img.jpg 1000w, img-2x.jpg 2000w'" width="536" height="354" />
47+
<!--or v-lazy:srcset="URL"-->
48+
</template>
49+
50+
<script>
51+
import logo from './assets/logo.png'
52+
53+
export default {
54+
data() {
55+
return {
56+
logo: logo
57+
}
58+
}
59+
}
60+
```
61+
62+
## Options
63+
64+
### useNative
65+
- Type: `Boolben`
66+
- Default: `true`
67+
- Required: `false`
68+
69+
Use the native image lazy-loading for the web
70+
71+
### rootMargin
72+
- Type: `String`
73+
- Default: `200px`
74+
- Required: `false`
75+
76+
rootMargin for IntersectionObserver
77+
78+
## Browser Support
79+
80+
Available in [latest browsers](http://caniuse.com/#feat=intersectionobserver). If browser support is not available, then make use of this [polyfill](https://www.npmjs.com/package/intersection-observer).
81+
82+
``` js
83+
require('intersection-observer')
84+
import LazyLoading from 'vue-lazy-loading'
85+
```
86+
87+
## License
88+
89+
[MIT](http://opensource.org/licenses/MIT)
90+
91+
## Keywords
92+
93+
vue lazy img iframe loading background-image

example/index.html

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,29 @@
1111
margin: 0 auto 10px;
1212
background-color: rosybrown;
1313
}
14+
.bg {
15+
display: block;
16+
margin: 0 auto 10px;
17+
width:536px;
18+
height:354px;
19+
background-color: rosybrown;
20+
font-size: 80px;
21+
color: aqua;
22+
text-align: center;
23+
line-height: 354px;
24+
}
1425
</style>
1526
</head>
1627
<body>
1728
<div id="app">
18-
<img v-for="n in 20" :key="n" :data-src="'https://picsum.photos/536/354?random=' + n" width="536" height="354">
29+
<div v-lazy:bg="'https://picsum.photos/536/354?random'" class="bg">background</div>
30+
<img v-for="n in 20" :key="n" v-lazy="'https://picsum.photos/536/354?random=' + n" width="536" height="354">
1931
</div>
2032
<script type="module">
2133
import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.esm.browser.min.js'
2234
import LazyLoading from '../dist/index.js'
2335

24-
Vue.use(LazyLoading, {
25-
useNative: true
26-
})
27-
36+
Vue.use(LazyLoading)
2837
new Vue({ el: '#app' })
2938
</script>
3039
</body>

src/index.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
1-
import { LazyOptions } from '../types/index'
1+
import { getVueVersion, LazyCore, error } from './util.js'
2+
import { LazyOptions } from '../types/index.js'
23

34
export default {
4-
install(Vue: any, options: LazyOptions = {
5-
useNative: true,
6-
}) {
7-
Vue.mixin({
8-
mounted() {
9-
if (!options.useNative || !('loading' in HTMLImageElement.prototype)) {
10-
console.log('IntersectionObserver');
5+
install(Vue: any, options: LazyOptions) {
6+
const lazy = new LazyCore(options)
7+
const version = getVueVersion(Vue)
8+
let config
119

12-
} else {
13-
const lazyEls = document.querySelectorAll('img, iframe')
14-
15-
lazyEls.forEach(lazyEl => {
16-
!lazyEl.getAttribute('loading') && lazyEl.setAttribute('loading', 'lazy')
17-
!lazyEl.getAttribute('src') && lazyEl.setAttribute('src', lazyEl.getAttribute('data-src'))
18-
})
19-
}
10+
if (version < 2) {
11+
error('Support version 2.X and 3.X')
12+
} else if (version === 2) {
13+
config = {
14+
bind: lazy.bind.bind(lazy),
15+
update: lazy.update.bind(lazy),
16+
unbind: lazy.unbind.bind(lazy),
17+
}
18+
} else {
19+
config = {
20+
mounted: lazy.bind.bind(lazy),
21+
updated: lazy.update.bind(lazy),
22+
unmounted: lazy.unbind.bind(lazy),
2023
}
21-
})
24+
}
25+
26+
Vue.directive('lazy', config)
2227
}
2328
}

src/observer.ts

Whitespace-only changes.

src/util.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { LazyOptions, LazyBinding, LazyElement } from '../types/index.js'
2+
3+
export class LazyCore {
4+
private useNative: boolean
5+
private rootMargin: string
6+
private type: 'loading' | 'observer' | 'none' = 'loading'
7+
private io?: IntersectionObserver
8+
9+
constructor(options: LazyOptions) {
10+
this.useNative = options?.useNative ?? true
11+
this.rootMargin = options?.rootMargin ?? '200px'
12+
this.init()
13+
}
14+
15+
private init() {
16+
if (this.useNative && 'loading' in HTMLImageElement.prototype) {
17+
this.type = 'loading'
18+
} else if (window.IntersectionObserver) {
19+
this.type = 'observer'
20+
this.setObserver()
21+
} else {
22+
this.type = 'none'
23+
error('Your browser does not support IntersectionObserver. https://github.com/tolking/vue-lazy-loading#Browser Support')
24+
}
25+
}
26+
27+
bind(el: Element, binding: LazyBinding) {
28+
!el.hasAttribute('loading') && el.setAttribute('loading', 'lazy')
29+
this.update(el, binding)
30+
}
31+
32+
update(el: Element, { oldValue, value, arg }: LazyBinding) {
33+
if (oldValue === value) return
34+
if (arg) {
35+
switch (arg) {
36+
case 'bg':
37+
case 'background':
38+
if ((el as LazyElement).style.backgroundImage) {
39+
(el as LazyElement).style.backgroundImage = ''
40+
}
41+
if (this.type === 'loading' || this.type === 'observer') {
42+
if (!this.io) {
43+
this.setObserver()
44+
}
45+
el.setAttribute('data-bg', value)
46+
this.io?.observe(el)
47+
} else {
48+
(el as LazyElement).style.backgroundImage = `url(${value})`
49+
}
50+
break;
51+
case 'set':
52+
case 'srcset':
53+
el.hasAttribute('srcset') && el.removeAttribute('srcset')
54+
if (this.type === 'loading') {
55+
el.setAttribute('srcset', value)
56+
} else if (this.type === 'observer') {
57+
el.setAttribute('data-srcset', value)
58+
this.io?.observe(el)
59+
} else {
60+
el.setAttribute('srcset', value)
61+
}
62+
break
63+
default:
64+
error('One of [v-lazy="URL", v-lazy:bg="URL", v-lazy:background="URL", v-lazy:set="URL"]')
65+
break;
66+
}
67+
} else {
68+
el.hasAttribute('src') && el.removeAttribute('src')
69+
if (this.type === 'loading') {
70+
el.setAttribute('src', value)
71+
} else if (this.type === 'observer') {
72+
el.setAttribute('data-src', value)
73+
this.io?.observe(el)
74+
} else {
75+
el.setAttribute('src', value)
76+
}
77+
}
78+
}
79+
80+
unbind(el: Element) {
81+
if (this.type === 'observer') {
82+
this.io?.unobserve(el)
83+
}
84+
}
85+
86+
private setObserver() {
87+
this.io = new IntersectionObserver(entries => {
88+
entries.forEach(item => {
89+
if (item.isIntersecting) {
90+
const src = (item.target as LazyElement).dataset?.src
91+
const srcset = (item.target as LazyElement).dataset?.srcset
92+
const bg = (item.target as LazyElement).dataset?.bg
93+
94+
if (src) {
95+
(item.target as LazyElement).src = src
96+
}
97+
if (srcset) {
98+
(item.target as LazyElement).srcset = srcset
99+
}
100+
if (bg) {
101+
(item.target as LazyElement).style.backgroundImage = `url(${bg})`
102+
}
103+
this.io?.unobserve(item.target)
104+
}
105+
})
106+
}, {
107+
rootMargin: this.rootMargin
108+
})
109+
}
110+
}
111+
112+
export function getVueVersion(Vue: any) {
113+
return Number(Vue.version.split('.')[0])
114+
}
115+
116+
export function error(msg: string) {
117+
process.env.NODE_ENV === 'development' &&
118+
console.error('[vue-lazy-loading error]: ' + msg);
119+
}

tsconfig.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
"compilerOptions": {
33
"target": "ES5",
44
"module":"ES2015",
5-
"lib": ["es2015", "es2016", "es2017", "dom"],
5+
"lib": ["ES2015", "ES2016", "ES2017", "DOM"],
66
"outDir": "dist",
7+
"strict": true,
78
"declaration": true,
8-
"sourceMap": true,
99
"removeComments": true
1010
},
1111
"include": ["src", "types"]

types/index.d.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1-
export interface LazyOptions {
2-
useNative: Boolean
1+
export type LazyOptions = undefined | {
2+
useNative?: boolean
3+
rootMargin?: string
4+
}
5+
6+
export interface LazyBinding {
7+
oldValue: string
8+
value: string
9+
arg?: 'bg' | 'background' | 'set' | 'srcset'
10+
}
11+
12+
export interface LazyElement extends HTMLElement {
13+
src: string
14+
srcset?: string
15+
dataset: {
16+
src?: string
17+
srcset?: string
18+
bg?: string
19+
}
320
}

0 commit comments

Comments
 (0)