Repository: hilongjw/vue-lazyload
Branch: master
Commit: 0d9a95ee9a09
Files: 26
Total size: 123.6 KB
Directory structure:
gitextract_27hlvzhc/
├── .babelrc
├── .circleci/
│ ├── config.yml
│ └── setup_puppeteer.sh
├── .editorconfig
├── .eslintrc
├── .github/
│ └── workflows/
│ └── codeql-analysis.yml
├── .gitignore
├── LICENSE
├── README.md
├── build.js
├── package.json
├── src/
│ ├── index.js
│ ├── lazy-component.js
│ ├── lazy-container.js
│ ├── lazy-image.js
│ ├── lazy.js
│ ├── listener.js
│ └── util.js
├── test/
│ └── test.spec.js
├── types/
│ ├── index.d.ts
│ ├── lazyload.d.ts
│ ├── test/
│ │ ├── index.ts
│ │ └── tsconfig.json
│ └── vue.d.ts
├── vue-lazyload.esm.js
└── vue-lazyload.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": [
["env", {
"modules": false
}]
],
"plugins": [
"syntax-dynamic-import",
"external-helpers"
],
"ignore": [
"dist/*.js"
]
}
================================================
FILE: .circleci/config.yml
================================================
# Javascript Node CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
version: 2
jobs:
build:
docker:
# specify the version you desire here
- image: circleci/node:10.23.3
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
# - image: circleci/mongo:3.4.4
working_directory: ~/repo
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run:
name: Workaround for GoogleChrome/puppeteer#290
command: 'sh .circleci/setup_puppeteer.sh'
- run: yarn install
- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}
# run tests!
- run: yarn lint
- run: yarn test
================================================
FILE: .circleci/setup_puppeteer.sh
================================================
#!/bin/bash
sudo apt-get update
sudo apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \
libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \
libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
================================================
FILE: .editorconfig
================================================
root = true
[*]
indent_style = space
indent_size = 4
tab_width = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
================================================
FILE: .eslintrc
================================================
{
"extends": "standard",
"env": {
"browser": true,
"es6": true,
"jest": true,
"mocha": true
}
}
================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '40 0 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
dist
lib
npm-debug.log
package-lock.json
.idea
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016 Awe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Vue-Lazyload
[](https://circleci.com/gh/hilongjw/vue-lazyload)
[](http://badge.fury.io/js/vue-lazyload)
[](http://badge.fury.io/js/vue-lazyload)
[](http://badge.fury.io/js/vue-lazyload)
[](http://makeapullrequest.com)
[](https://cdnjs.com/libraries/vue-lazyload)
Vue module for lazyloading images in your applications. Some of goals of this project worth noting include:
* Be lightweight, powerful and easy to use
* Work on any image type
* Add loading class while image is loading
* Supports both of Vue 1.0 and Vue 2.0
# For Vue 3
Please use vue-lazyload@3.x, see [here](https://github.com/hilongjw/vue-lazyload/tree/next)
# Table of Contents
* [___Demo___](#demo)
* [___Requirements___](#requirements)
* [___Installation___](#installation)
* [___Usage___](#usage)
* [___Constructor Options___](#constructor-options)
* [___Implementation___](#implementation)
* [___Basic___](#basic)
* [___Css state___](#css-state)
* [___Methods___](#methods)
* [__Event hook__](#event-hook)
* [__LazyLoadHandler__](#lazyloadhandler)
* [__Performance__](#performance)
* [___Authors && Contributors___](#authors-&&-Contributors)
* [___License___](#license)
# Demo
[___Demo___](http://hilongjw.github.io/vue-lazyload/)
# Requirements
- [Vue.js](https://github.com/vuejs/vue) `1.x` or `2.x`
# Installation
## npm
```bash
$ npm i vue-lazyload -S
```
## yarn
```bash
$ yarn add vue-lazyload
```
## CDN
CDN: [https://unpkg.com/vue-lazyload/vue-lazyload.js](https://unpkg.com/vue-lazyload/vue-lazyload.js)
```html
```
# Usage
main.js:
```javascript
import Vue from 'vue'
import App from './App.vue'
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload)
// or with options
const loadimage = require('./assets/loading.gif')
const errorimage = require('./assets/error.gif')
Vue.use(VueLazyload, {
preLoad: 1.3,
error: errorimage,
loading: loadimage,
attempt: 1
})
new Vue({
el: 'body',
components: {
App
}
})
```
template:
```html
-
```
use `v-lazy-container` work with raw HTML
```html
```
custom `error` and `loading` placeholder image
```html
```
```html
```
## Constructor Options
|key|description|default|options|
|:---|---|---|---|
| `preLoad`|proportion of pre-loading height|`1.3`|`Number`|
|`error`|src of the image upon load fail|`'data-src'`|`String`
|`loading`|src of the image while loading|`'data-src'`|`String`|
|`attempt`|attempts count|`3`|`Number`|
|`listenEvents`|events that you want vue listen for|`['scroll', 'wheel', 'mousewheel', 'resize', 'animationend', 'transitionend', 'touchmove']`| [Desired Listen Events](#desired-listen-events) |
|`adapter`| dynamically modify the attribute of element |`{ }`| [Element Adapter](#element-adapter) |
|`filter`| the image's listener filter |`{ }`| [Image listener filter](#image-listener-filter) |
|`lazyComponent`| lazyload component | `false` | [Lazy Component](#lazy-component)
| `dispatchEvent`|trigger the dom event|`false`|`Boolean`|
| `throttleWait`|throttle wait|`200`|`Number`|
| `observer`|use IntersectionObserver|`false`|`Boolean`|
| `observerOptions`|IntersectionObserver options|{ rootMargin: '0px', threshold: 0.1 }|[IntersectionObserver](#intersectionobserver)|
| `silent`|do not print debug info|`true`|`Boolean`|
### Desired Listen Events
You can configure which events you want vue-lazyload by passing in an array
of listener names.
```javascript
Vue.use(VueLazyload, {
preLoad: 1.3,
error: 'dist/error.png',
loading: 'dist/loading.gif',
attempt: 1,
// the default is ['scroll', 'wheel', 'mousewheel', 'resize', 'animationend', 'transitionend']
listenEvents: [ 'scroll' ]
})
```
This is useful if you are having trouble with this plugin resetting itself to loading
when you have certain animations and transitions taking place
### Image listener filter
dynamically modify the src of image
```javascript
Vue.use(vueLazy, {
filter: {
progressive (listener, options) {
const isCDN = /qiniudn.com/
if (isCDN.test(listener.src)) {
listener.el.setAttribute('lazy-progressive', 'true')
listener.loading = listener.src + '?imageView2/1/w/10/h/10'
}
},
webp (listener, options) {
if (!options.supportWebp) return
const isCDN = /qiniudn.com/
if (isCDN.test(listener.src)) {
listener.src += '?imageView2/2/format/webp'
}
}
}
})
```
### Element Adapter
```javascript
Vue.use(vueLazy, {
adapter: {
loaded ({ bindType, el, naturalHeight, naturalWidth, $parent, src, loading, error, Init }) {
// do something here
// example for call LoadedHandler
LoadedHandler(el)
},
loading (listender, Init) {
console.log('loading')
},
error (listender, Init) {
console.log('error')
}
}
})
```
### IntersectionObserver
use [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) to to improve performance of a large number of nodes.
```javascript
Vue.use(vueLazy, {
// set observer to true
observer: true,
// optional
observerOptions: {
rootMargin: '0px',
threshold: 0.1
}
})
```
### Lazy Component
```javascript
Vue.use(VueLazyload, {
lazyComponent: true
});
```
```html
```
Use in list
```html
```
## Implementation
### Basic
vue-lazyload will set this img element's `src` with `imgUrl` string
```html
```
### CSS state
There are three states while img loading
`loading` `loaded` `error`
```html
```
```html
```
## Methods
### Event Hook
`vm.$Lazyload.$on(event, callback)`
`vm.$Lazyload.$off(event, callback)`
`vm.$Lazyload.$once(event, callback)`
- `$on` Listen for a custom events `loading`, `loaded`, `error`
- `$once` Listen for a custom event, but only once. The listener will be removed once it triggers for the first time.
- `$off` Remove event listener(s).
#### `vm.$Lazyload.$on`
#### Arguments:
* `{string} event`
* `{Function} callback`
#### Example
```javascript
vm.$Lazyload.$on('loaded', function ({ bindType, el, naturalHeight, naturalWidth, $parent, src, loading, error }, formCache) {
console.log(el, src)
})
```
#### `vm.$Lazyload.$once`
#### Arguments:
* `{string} event`
* `{Function} callback`
#### Example
```javascript
vm.$Lazyload.$once('loaded', function ({ el, src }) {
console.log(el, src)
})
```
#### `vm.$Lazyload.$off`
If only the event is provided, remove all listeners for that event
#### Arguments:
* `{string} event`
* `{Function} callback`
#### Example
```javascript
function handler ({ el, src }, formCache) {
console.log(el, src)
}
vm.$Lazyload.$on('loaded', handler)
vm.$Lazyload.$off('loaded', handler)
vm.$Lazyload.$off('loaded')
```
### LazyLoadHandler
`vm.$Lazyload.lazyLoadHandler`
Manually trigger lazy loading position calculation
#### Example
```javascript
this.$Lazyload.lazyLoadHandler()
```
### Performance
```javascript
this.$Lazyload.$on('loaded', function (listener) {
console.table(this.$Lazyload.performance())
})
```

### Dynamic switching pictures
```vue
```
# Authors && Contributors
- [hilongjw](https://github.com/hilongjw)
- [imcvampire](https://github.com/imcvampire)
- [darrynten](https://github.com/darrynten)
- [biluochun](https://github.com/biluochun)
- [whwnow](https://github.com/whwnow)
- [Leopoldthecoder](https://github.com/Leopoldthecoder)
- [michalbcz](https://github.com/michalbcz)
- [blue0728](https://github.com/blue0728)
- [JounQin](https://github.com/JounQin)
- [llissery](https://github.com/llissery)
- [mega667](https://github.com/mega667)
- [RobinCK](https://github.com/RobinCK)
- [GallenHu](https://github.com/GallenHu)
# License
[The MIT License](http://opensource.org/licenses/MIT)
================================================
FILE: build.js
================================================
const path = require('path')
const rollup = require('rollup')
const babel = require('rollup-plugin-babel')
const replace = require('@rollup/plugin-replace')
const { terser } = require('rollup-plugin-terser')
const resolve = require('rollup-plugin-node-resolve')
const commonjs = require('rollup-plugin-commonjs')
const version = process.env.VERSION || require('./package.json').version
const banner =
'/*!\n' +
' * Vue-Lazyload.js v' + version + '\n' +
' * (c) ' + new Date().getFullYear() + ' Awe \n' +
' * Released under the MIT License.\n' +
' */\n'
async function build (options, _outputOptions) {
try {
const bundle = await rollup.rollup(options)
const outputOptions = {
format: _outputOptions.format,
exports: 'named',
banner: banner,
file: path.resolve(__dirname, _outputOptions.filename),
name: 'VueLazyload'
}
const { output } = await bundle.generate(outputOptions)
await bundle.write(outputOptions)
const code = output[0].code
console.log(blue(outputOptions.file) + ' ' + getSize(code))
} catch (e) {
console.error(e)
}
}
function getSize (code) {
return (Buffer.byteLength(code, 'utf8') / 1024).toFixed(2) + 'kb'
}
function blue (str) {
return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m'
}
build({
input: path.resolve(__dirname, 'src/index.js'),
plugins: [
resolve(),
commonjs(),
babel({ runtimeHelpers: true }),
replace({
'__VUE_LAZYLOAD_VERSION__': JSON.stringify(version)
}),
terser()
]
}, {
format: 'umd',
filename: 'vue-lazyload.js'
})
build({
input: path.resolve(__dirname, 'src/index.js'),
plugins: [
resolve(),
commonjs(),
replace({
'__VUE_LAZYLOAD_VERSION__': JSON.stringify(version)
}),
babel({ runtimeHelpers: true })
]
}, {
format: 'esm',
filename: 'vue-lazyload.esm.js'
})
================================================
FILE: package.json
================================================
{
"name": "vue-lazyload",
"version": "1.3.5",
"description": "Vue module for lazy-loading images in your vue.js applications.",
"main": "vue-lazyload.js",
"module": "vue-lazyload.esm.js",
"unpkg": "vue-lazyload.js",
"scripts": {
"build": "node build",
"lint": "eslint ./src",
"test": "jest"
},
"dependencies": {},
"repository": {
"type": "git",
"url": "https://github.com/hilongjw/vue-lazyload.git"
},
"typings": "types/index.d.ts",
"keywords": [
"vue-lazyload",
"vue",
"lazyload",
"vue-directive"
],
"author": "Awe ",
"bugs": {
"url": "https://github.com/hilongjw/vue-lazyload/issues"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
"license": "MIT",
"jest": {
"setupFiles": [
"jest-canvas-mock"
]
},
"devDependencies": {
"@rollup/plugin-replace": "^2.3.4",
"assign-deep": "^1.0.1",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-plugin-external-helpers": "^6.22.0",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-preset-stage-0": "^6.24.1",
"babel-register": "^6.26.0",
"chai": "^4.3.0",
"eslint": "^4.19.1",
"eslint-config-standard": "^11.0.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^5.2.1",
"eslint-plugin-promise": "^3.8.0",
"eslint-plugin-standard": "^3.1.0",
"jest": "^26.6.3",
"jest-canvas-mock": "^2.3.1",
"mocha": "^4.0.1",
"rollup": "^2.39.0",
"rollup-plugin-babel": "^2.6.1",
"rollup-plugin-commonjs": "^8.4.1",
"rollup-plugin-node-resolve": "^3.4.0",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-uglify": "^1.0.1",
"vue": "^2.6.12"
}
}
================================================
FILE: src/index.js
================================================
import Lazy from './lazy'
import LazyComponent from './lazy-component'
import LazyContainer from './lazy-container'
import LazyImage from './lazy-image'
import { assign } from './util'
export default {
/*
* install function
* @param {Vue} Vue
* @param {object} options lazyload options
*/
install (Vue, options = {}) {
const LazyClass = Lazy(Vue)
const lazy = new LazyClass(options)
const lazyContainer = new LazyContainer({ lazy })
const isVue2 = Vue.version.split('.')[0] === '2'
Vue.prototype.$Lazyload = lazy
if (options.lazyComponent) {
Vue.component('lazy-component', LazyComponent(lazy))
}
if (options.lazyImage) {
Vue.component('lazy-image', LazyImage(lazy))
}
if (isVue2) {
Vue.directive('lazy', {
bind: lazy.add.bind(lazy),
update: lazy.update.bind(lazy),
componentUpdated: lazy.lazyLoadHandler.bind(lazy),
unbind: lazy.remove.bind(lazy)
})
Vue.directive('lazy-container', {
bind: lazyContainer.bind.bind(lazyContainer),
componentUpdated: lazyContainer.update.bind(lazyContainer),
unbind: lazyContainer.unbind.bind(lazyContainer)
})
} else {
Vue.directive('lazy', {
bind: lazy.lazyLoadHandler.bind(lazy),
update (newValue, oldValue) {
assign(this.vm.$refs, this.vm.$els)
lazy.add(this.el, {
modifiers: this.modifiers || {},
arg: this.arg,
value: newValue,
oldValue: oldValue
}, {
context: this.vm
})
},
unbind () {
lazy.remove(this.el)
}
})
Vue.directive('lazy-container', {
update (newValue, oldValue) {
lazyContainer.update(this.el, {
modifiers: this.modifiers || {},
arg: this.arg,
value: newValue,
oldValue: oldValue
}, {
context: this.vm
})
},
unbind () {
lazyContainer.unbind(this.el)
}
})
}
}
}
export {
Lazy,
LazyComponent,
LazyImage,
LazyContainer
}
================================================
FILE: src/lazy-component.js
================================================
import { inBrowser } from './util'
import Lazy from './lazy'
const LazyComponent = (lazy) => {
return {
props: {
tag: {
type: String,
default: 'div'
}
},
render (h) {
return h(this.tag, null, this.show ? this.$slots.default : null)
},
data () {
return {
el: null,
state: {
loaded: false
},
rect: {},
show: false
}
},
mounted () {
this.el = this.$el
lazy.addLazyBox(this)
lazy.lazyLoadHandler()
},
beforeDestroy () {
lazy.removeComponent(this)
},
methods: {
getRect () {
this.rect = this.$el.getBoundingClientRect()
},
checkInView () {
this.getRect()
return inBrowser &&
(this.rect.top < window.innerHeight * lazy.options.preLoad && this.rect.bottom > 0) &&
(this.rect.left < window.innerWidth * lazy.options.preLoad && this.rect.right > 0)
},
load () {
this.show = true
this.state.loaded = true
this.$emit('show', this)
},
destroy () {
return this.$destroy
}
}
}
}
LazyComponent.install = function (Vue, options = {}) {
const LazyClass = Lazy(Vue)
const lazy = new LazyClass(options)
Vue.component('lazy-component', LazyComponent(lazy))
}
export default LazyComponent
================================================
FILE: src/lazy-container.js
================================================
import {
find,
remove,
assign,
ArrayFrom
} from './util'
import Lazy from './lazy'
export default class LazyContainerMananger {
constructor ({ lazy }) {
this.lazy = lazy
lazy.lazyContainerMananger = this
this._queue = []
}
bind (el, binding, vnode) {
const container = new LazyContainer({ el, binding, vnode, lazy: this.lazy })
this._queue.push(container)
}
update (el, binding, vnode) {
const container = find(this._queue, item => item.el === el)
if (!container) return
container.update({ el, binding, vnode })
}
unbind (el, binding, vnode) {
const container = find(this._queue, item => item.el === el)
if (!container) return
container.clear()
remove(this._queue, container)
}
}
const defaultOptions = {
selector: 'img'
}
class LazyContainer {
constructor ({ el, binding, vnode, lazy }) {
this.el = null
this.vnode = vnode
this.binding = binding
this.options = {}
this.lazy = lazy
this._queue = []
this.update({ el, binding })
}
update ({ el, binding }) {
this.el = el
this.options = assign({}, defaultOptions, binding.value)
const imgs = this.getImgs()
imgs.forEach(el => {
this.lazy.add(el, assign({}, this.binding, {
value: {
src: 'dataset' in el ? el.dataset.src : el.getAttribute('data-src'),
error: ('dataset' in el ? el.dataset.error : el.getAttribute('data-error')) || this.options.error,
loading: ('dataset' in el ? el.dataset.loading : el.getAttribute('data-loading')) || this.options.loading
}
}), this.vnode)
})
}
getImgs () {
return ArrayFrom(this.el.querySelectorAll(this.options.selector))
}
clear () {
const imgs = this.getImgs()
imgs.forEach(el => this.lazy.remove(el))
this.vnode = null
this.binding = null
this.lazy = null
}
}
LazyContainer.install = (Vue, options = {}) => {
const LazyClass = Lazy(Vue)
const lazy = new LazyClass(options)
const lazyContainer = new LazyContainer({ lazy })
const isVue2 = Vue.version.split('.')[0] === '2'
if (isVue2) {
Vue.directive('lazy-container', {
bind: lazyContainer.bind.bind(lazyContainer),
componentUpdated: lazyContainer.update.bind(lazyContainer),
unbind: lazyContainer.unbind.bind(lazyContainer)
})
} else {
Vue.directive('lazy-container', {
update (newValue, oldValue) {
lazyContainer.update(this.el, {
modifiers: this.modifiers || {},
arg: this.arg,
value: newValue,
oldValue: oldValue
}, {
context: this.vm
})
},
unbind () {
lazyContainer.unbind(this.el)
}
})
}
}
================================================
FILE: src/lazy-image.js
================================================
import {
inBrowser,
loadImageAsync,
noop
} from './util'
import Lazy from './lazy'
const LazyImage = (lazyManager) => {
return {
props: {
src: [String, Object],
tag: {
type: String,
default: 'img'
}
},
render (h) {
return h(this.tag, {
attrs: {
src: this.renderSrc
}
}, this.$slots.default)
},
data () {
return {
el: null,
options: {
src: '',
error: '',
loading: '',
attempt: lazyManager.options.attempt
},
state: {
loaded: false,
error: false,
attempt: 0
},
rect: {},
renderSrc: ''
}
},
watch: {
src () {
this.init()
lazyManager.addLazyBox(this)
lazyManager.lazyLoadHandler()
}
},
created () {
this.init()
this.renderSrc = this.options.loading
},
mounted () {
this.el = this.$el
lazyManager.addLazyBox(this)
lazyManager.lazyLoadHandler()
},
beforeDestroy () {
lazyManager.removeComponent(this)
},
methods: {
init () {
const { src, loading, error } = lazyManager._valueFormatter(this.src)
this.state.loaded = false
this.options.src = src
this.options.error = error
this.options.loading = loading
this.renderSrc = this.options.loading
},
getRect () {
this.rect = this.$el.getBoundingClientRect()
},
checkInView () {
this.getRect()
return inBrowser &&
(this.rect.top < window.innerHeight * lazyManager.options.preLoad && this.rect.bottom > 0) &&
(this.rect.left < window.innerWidth * lazyManager.options.preLoad && this.rect.right > 0)
},
load (onFinish = noop) {
if ((this.state.attempt > this.options.attempt - 1) && this.state.error) {
if (!lazyManager.options.silent) console.log(`VueLazyload log: ${this.options.src} tried too more than ${this.options.attempt} times`)
onFinish()
return
}
const src = this.options.src
loadImageAsync({ src }, ({ src }) => {
this.renderSrc = src
this.state.loaded = true
}, e => {
this.state.attempt++
this.renderSrc = this.options.error
this.state.error = true
})
}
}
}
}
LazyImage.install = (Vue, options = {}) => {
const LazyClass = Lazy(Vue)
const lazy = new LazyClass(options)
Vue.component('lazy-image', LazyImage(lazy))
}
export default LazyImage
================================================
FILE: src/lazy.js
================================================
import {
inBrowser,
CustomEvent,
remove,
some,
find,
_,
throttle,
supportWebp,
getDPR,
scrollParent,
getBestSelectionFromSrcset,
assign,
isObject,
hasIntersectionObserver,
modeType,
ImageCache
} from './util'
import ReactiveListener from './listener'
const DEFAULT_URL = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
const DEFAULT_EVENTS = ['scroll', 'wheel', 'mousewheel', 'resize', 'animationend', 'transitionend', 'touchmove']
const DEFAULT_OBSERVER_OPTIONS = {
rootMargin: '0px',
threshold: 0
}
export default function Lazy (Vue) {
return class Lazy {
constructor ({ preLoad, error, throttleWait, preLoadTop, dispatchEvent, loading, attempt, silent = true, scale, listenEvents, hasbind, filter, adapter, observer, observerOptions }) {
this.version = '__VUE_LAZYLOAD_VERSION__'
this.mode = modeType.event
this.ListenerQueue = []
this.TargetIndex = 0
this.TargetQueue = []
this.options = {
silent: silent,
dispatchEvent: !!dispatchEvent,
throttleWait: throttleWait || 200,
preLoad: preLoad || 1.3,
preLoadTop: preLoadTop || 0,
error: error || DEFAULT_URL,
loading: loading || DEFAULT_URL,
attempt: attempt || 3,
scale: scale || getDPR(scale),
ListenEvents: listenEvents || DEFAULT_EVENTS,
hasbind: false,
supportWebp: supportWebp(),
filter: filter || {},
adapter: adapter || {},
observer: !!observer,
observerOptions: observerOptions || DEFAULT_OBSERVER_OPTIONS
}
this._initEvent()
this._imageCache = new ImageCache({ max: 200 })
this.lazyLoadHandler = throttle(this._lazyLoadHandler.bind(this), this.options.throttleWait)
this.setMode(this.options.observer ? modeType.observer : modeType.event)
}
/**
* update config
* @param {Object} config params
* @return
*/
config (options = {}) {
assign(this.options, options)
}
/**
* output listener's load performance
* @return {Array}
*/
performance () {
let list = []
this.ListenerQueue.map(item => {
list.push(item.performance())
})
return list
}
/*
* add lazy component to queue
* @param {Vue} vm lazy component instance
* @return
*/
addLazyBox (vm) {
this.ListenerQueue.push(vm)
if (inBrowser) {
this._addListenerTarget(window)
this._observer && this._observer.observe(vm.el)
if (vm.$el && vm.$el.parentNode) {
this._addListenerTarget(vm.$el.parentNode)
}
}
}
/*
* add image listener to queue
* @param {DOM} el
* @param {object} binding vue directive binding
* @param {vnode} vnode vue directive vnode
* @return
*/
add (el, binding, vnode) {
if (some(this.ListenerQueue, item => item.el === el)) {
this.update(el, binding)
return Vue.nextTick(this.lazyLoadHandler)
}
let { src, loading, error, cors } = this._valueFormatter(binding.value)
Vue.nextTick(() => {
src = getBestSelectionFromSrcset(el, this.options.scale) || src
this._observer && this._observer.observe(el)
const container = Object.keys(binding.modifiers)[0]
let $parent
if (container) {
$parent = vnode.context.$refs[container]
// if there is container passed in, try ref first, then fallback to getElementById to support the original usage
$parent = $parent ? $parent.$el || $parent : document.getElementById(container)
}
if (!$parent) {
$parent = scrollParent(el)
}
const newListener = new ReactiveListener({
bindType: binding.arg,
$parent,
el,
loading,
error,
src,
cors,
elRenderer: this._elRenderer.bind(this),
options: this.options,
imageCache: this._imageCache
})
this.ListenerQueue.push(newListener)
if (inBrowser) {
this._addListenerTarget(window)
this._addListenerTarget($parent)
}
this.lazyLoadHandler()
Vue.nextTick(() => this.lazyLoadHandler())
})
}
/**
* update image src
* @param {DOM} el
* @param {object} vue directive binding
* @return
*/
update (el, binding, vnode) {
let { src, loading, error } = this._valueFormatter(binding.value)
src = getBestSelectionFromSrcset(el, this.options.scale) || src
const exist = find(this.ListenerQueue, item => item.el === el)
if (!exist) {
this.add(el, binding, vnode)
} else {
exist.update({
src,
loading,
error
})
}
if (this._observer) {
this._observer.unobserve(el)
this._observer.observe(el)
}
this.lazyLoadHandler()
Vue.nextTick(() => this.lazyLoadHandler())
}
/**
* remove listener form list
* @param {DOM} el
* @return
*/
remove (el) {
if (!el) return
this._observer && this._observer.unobserve(el)
const existItem = find(this.ListenerQueue, item => item.el === el)
if (existItem) {
this._removeListenerTarget(existItem.$parent)
this._removeListenerTarget(window)
remove(this.ListenerQueue, existItem)
existItem.$destroy()
}
}
/*
* remove lazy components form list
* @param {Vue} vm Vue instance
* @return
*/
removeComponent (vm) {
if (!vm) return
remove(this.ListenerQueue, vm)
this._observer && this._observer.unobserve(vm.el)
if (vm.$parent && vm.$el.parentNode) {
this._removeListenerTarget(vm.$el.parentNode)
}
this._removeListenerTarget(window)
}
setMode (mode) {
if (!hasIntersectionObserver && mode === modeType.observer) {
mode = modeType.event
}
this.mode = mode // event or observer
if (mode === modeType.event) {
if (this._observer) {
this.ListenerQueue.forEach(listener => {
this._observer.unobserve(listener.el)
})
this._observer = null
}
this.TargetQueue.forEach(target => {
this._initListen(target.el, true)
})
} else {
this.TargetQueue.forEach(target => {
this._initListen(target.el, false)
})
this._initIntersectionObserver()
}
}
/*
*** Private functions ***
*/
/*
* add listener target
* @param {DOM} el listener target
* @return
*/
_addListenerTarget (el) {
if (!el) return
let target = find(this.TargetQueue, target => target.el === el)
if (!target) {
target = {
el: el,
id: ++this.TargetIndex,
childrenCount: 1,
listened: true
}
this.mode === modeType.event && this._initListen(target.el, true)
this.TargetQueue.push(target)
} else {
target.childrenCount++
}
return this.TargetIndex
}
/*
* remove listener target or reduce target childrenCount
* @param {DOM} el or window
* @return
*/
_removeListenerTarget (el) {
this.TargetQueue.forEach((target, index) => {
if (target.el === el) {
target.childrenCount--
if (!target.childrenCount) {
this._initListen(target.el, false)
this.TargetQueue.splice(index, 1)
target = null
}
}
})
}
/*
* add or remove eventlistener
* @param {DOM} el DOM or Window
* @param {boolean} start flag
* @return
*/
_initListen (el, start) {
this.options.ListenEvents.forEach((evt) => _[start ? 'on' : 'off'](el, evt, this.lazyLoadHandler))
}
_initEvent () {
this.Event = {
listeners: {
loading: [],
loaded: [],
error: []
}
}
this.$on = (event, func) => {
if (!this.Event.listeners[event]) this.Event.listeners[event] = []
this.Event.listeners[event].push(func)
}
this.$once = (event, func) => {
const vm = this
function on () {
vm.$off(event, on)
func.apply(vm, arguments)
}
this.$on(event, on)
}
this.$off = (event, func) => {
if (!func) {
if (!this.Event.listeners[event]) return
this.Event.listeners[event].length = 0
return
}
remove(this.Event.listeners[event], func)
}
this.$emit = (event, context, inCache) => {
if (!this.Event.listeners[event]) return
this.Event.listeners[event].forEach(func => func(context, inCache))
}
}
/**
* find nodes which in viewport and trigger load
* @return
*/
_lazyLoadHandler () {
const freeList = []
this.ListenerQueue.forEach((listener, index) => {
if (!listener.el || !listener.el.parentNode) {
freeList.push(listener)
}
const catIn = listener.checkInView()
if (!catIn) return
listener.load()
})
freeList.forEach(item => {
remove(this.ListenerQueue, item)
item.$destroy()
})
}
/**
* init IntersectionObserver
* set mode to observer
* @return
*/
_initIntersectionObserver () {
if (!hasIntersectionObserver) return
this._observer = new IntersectionObserver(this._observerHandler.bind(this), this.options.observerOptions)
if (this.ListenerQueue.length) {
this.ListenerQueue.forEach(listener => {
this._observer.observe(listener.el)
})
}
}
/**
* init IntersectionObserver
* @return
*/
_observerHandler (entries, observer) {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.ListenerQueue.forEach(listener => {
if (listener.el === entry.target) {
if (listener.state.loaded) return this._observer.unobserve(listener.el)
listener.load()
}
})
}
})
}
/**
* set element attribute with image'url and state
* @param {object} lazyload listener object
* @param {string} state will be rendered
* @param {bool} inCache is rendered from cache
* @return
*/
_elRenderer (listener, state, cache) {
if (!listener.el) return
const { el, bindType } = listener
let src
switch (state) {
case 'loading':
src = listener.loading
break
case 'error':
src = listener.error
break
default:
src = listener.src
break
}
if (bindType) {
el.style[bindType] = 'url("' + src + '")'
} else if (el.getAttribute('src') !== src) {
el.setAttribute('src', src)
}
el.setAttribute('lazy', state)
this.$emit(state, listener, cache)
this.options.adapter[state] && this.options.adapter[state](listener, this.options)
if (this.options.dispatchEvent) {
const event = new CustomEvent(state, {
detail: listener
})
el.dispatchEvent(event)
}
}
/**
* generate loading loaded error image url
* @param {string} image's src
* @return {object} image's loading, loaded, error url
*/
_valueFormatter (value) {
let src = value
let loading = this.options.loading
let error = this.options.error
// value is object
if (isObject(value)) {
if (!value.src && !this.options.silent) console.error('Vue Lazyload warning: miss src with ' + value)
src = value.src
loading = value.loading || this.options.loading
error = value.error || this.options.error
}
return {
src,
loading,
error
}
}
}
}
Lazy.install = (Vue, options = {}) => {
const LazyClass = Lazy(Vue)
const lazy = new LazyClass(options)
const isVue2 = Vue.version.split('.')[0] === '2'
if (isVue2) {
Vue.directive('lazy', {
bind: lazy.add.bind(lazy),
update: lazy.update.bind(lazy),
componentUpdated: lazy.lazyLoadHandler.bind(lazy),
unbind: lazy.remove.bind(lazy)
})
} else {
Vue.directive('lazy', {
bind: lazy.lazyLoadHandler.bind(lazy),
update (newValue, oldValue) {
assign(this.vm.$refs, this.vm.$els)
lazy.add(this.el, {
modifiers: this.modifiers || {},
arg: this.arg,
value: newValue,
oldValue: oldValue
}, {
context: this.vm
})
},
unbind () {
lazy.remove(this.el)
}
})
}
}
================================================
FILE: src/listener.js
================================================
import {
loadImageAsync,
ObjectKeys,
noop
} from './util'
// el: {
// state,
// src,
// error,
// loading
// }
export default class ReactiveListener {
constructor ({ el, src, error, loading, bindType, $parent, options, cors, elRenderer, imageCache }) {
this.el = el
this.src = src
this.error = error
this.loading = loading
this.bindType = bindType
this.attempt = 0
this.cors = cors
this.naturalHeight = 0
this.naturalWidth = 0
this.options = options
this.rect = null
this.$parent = $parent
this.elRenderer = elRenderer
this._imageCache = imageCache
this.performanceData = {
init: Date.now(),
loadStart: 0,
loadEnd: 0
}
this.filter()
this.initState()
this.render('loading', false)
}
/*
* init listener state
* @return
*/
initState () {
if ('dataset' in this.el) {
this.el.dataset.src = this.src
} else {
this.el.setAttribute('data-src', this.src)
}
this.state = {
loading: false,
error: false,
loaded: false,
rendered: false
}
}
/*
* record performance
* @return
*/
record (event) {
this.performanceData[event] = Date.now()
}
/*
* update image listener data
* @param {String} image uri
* @param {String} loading image uri
* @param {String} error image uri
* @return
*/
update ({ src, loading, error }) {
const oldSrc = this.src
this.src = src
this.loading = loading
this.error = error
this.filter()
if (oldSrc !== this.src) {
this.attempt = 0
this.initState()
}
}
/*
* get el node rect
* @return
*/
getRect () {
this.rect = this.el.getBoundingClientRect()
}
/*
* check el is in view
* @return {Boolean} el is in view
*/
checkInView () {
this.getRect()
return (this.rect.top < window.innerHeight * this.options.preLoad && this.rect.bottom > this.options.preLoadTop) &&
(this.rect.left < window.innerWidth * this.options.preLoad && this.rect.right > 0)
}
/*
* listener filter
*/
filter () {
ObjectKeys(this.options.filter).map(key => {
this.options.filter[key](this, this.options)
})
}
/*
* render loading first
* @params cb:Function
* @return
*/
renderLoading (cb) {
this.state.loading = true
loadImageAsync({
src: this.loading,
cors: this.cors
}, data => {
this.render('loading', false)
this.state.loading = false
cb()
}, () => {
// handler `loading image` load failed
cb()
this.state.loading = false
if (!this.options.silent) console.warn(`VueLazyload log: load failed with loading image(${this.loading})`)
})
}
/*
* try load image and render it
* @return
*/
load (onFinish = noop) {
if ((this.attempt > this.options.attempt - 1) && this.state.error) {
if (!this.options.silent) console.log(`VueLazyload log: ${this.src} tried too more than ${this.options.attempt} times`)
onFinish()
return
}
if (this.state.rendered && this.state.loaded) return
if (this._imageCache.has(this.src)) {
this.state.loaded = true
this.render('loaded', true)
this.state.rendered = true
return onFinish()
}
this.renderLoading(() => {
this.attempt++
this.options.adapter['beforeLoad'] && this.options.adapter['beforeLoad'](this, this.options)
this.record('loadStart')
loadImageAsync({
src: this.src,
cors: this.cors
}, data => {
this.naturalHeight = data.naturalHeight
this.naturalWidth = data.naturalWidth
this.state.loaded = true
this.state.error = false
this.record('loadEnd')
this.render('loaded', false)
this.state.rendered = true
this._imageCache.add(this.src)
onFinish()
}, err => {
!this.options.silent && console.error(err)
this.state.error = true
this.state.loaded = false
this.render('error', false)
})
})
}
/*
* render image
* @param {String} state to render // ['loading', 'src', 'error']
* @param {String} is form cache
* @return
*/
render (state, cache) {
this.elRenderer(this, state, cache)
}
/*
* output performance data
* @return {Object} performance data
*/
performance () {
let state = 'loading'
let time = 0
if (this.state.loaded) {
state = 'loaded'
time = (this.performanceData.loadEnd - this.performanceData.loadStart) / 1000
}
if (this.state.error) state = 'error'
return {
src: this.src,
state,
time
}
}
/*
* $destroy
* @return
*/
$destroy () {
this.el = null
this.src = null
this.error = null
this.loading = null
this.bindType = null
this.attempt = 0
}
}
================================================
FILE: src/util.js
================================================
import assign from 'assign-deep'
const inBrowser = typeof window !== 'undefined' && window !== null
export const hasIntersectionObserver = checkIntersectionObserver()
function checkIntersectionObserver () {
if (inBrowser &&
'IntersectionObserver' in window &&
'IntersectionObserverEntry' in window &&
'intersectionRatio' in window.IntersectionObserverEntry.prototype) {
// Minimal polyfill for Edge 15's lack of `isIntersecting`
// See: https://github.com/w3c/IntersectionObserver/issues/211
if (!('isIntersecting' in window.IntersectionObserverEntry.prototype)) {
Object.defineProperty(window.IntersectionObserverEntry.prototype,
'isIntersecting', {
get: function () {
return this.intersectionRatio > 0
}
})
}
return true
}
return false
}
export const modeType = {
event: 'event',
observer: 'observer'
}
// CustomEvent polyfill for IE
const CustomEvent = (function () {
if (!inBrowser) return
// not IE
if (typeof window.CustomEvent === 'function') return window.CustomEvent
function CustomEvent (event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined }
var evt = document.createEvent('CustomEvent')
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail)
return evt
}
CustomEvent.prototype = window.Event.prototype
return CustomEvent
})()
function remove (arr, item) {
if (!arr.length) return
const index = arr.indexOf(item)
if (index > -1) return arr.splice(index, 1)
}
function some (arr, fn) {
let has = false
for (let i = 0, len = arr.length; i < len; i++) {
if (fn(arr[i])) {
has = true
break
}
}
return has
}
function getBestSelectionFromSrcset (el, scale) {
if (el.tagName !== 'IMG' || !el.getAttribute('data-srcset')) return
let options = el.getAttribute('data-srcset')
const result = []
const container = el.parentNode
const containerWidth = container.offsetWidth * scale
let spaceIndex
let tmpSrc
let tmpWidth
options = options.trim().split(',')
options.map(item => {
item = item.trim()
spaceIndex = item.lastIndexOf(' ')
if (spaceIndex === -1) {
tmpSrc = item
tmpWidth = 999998
} else {
tmpSrc = item.substr(0, spaceIndex)
tmpWidth = parseInt(item.substr(spaceIndex + 1, item.length - spaceIndex - 2), 10)
}
result.push([tmpWidth, tmpSrc])
})
result.sort(function (a, b) {
if (a[0] < b[0]) {
return 1
}
if (a[0] > b[0]) {
return -1
}
if (a[0] === b[0]) {
if (b[1].indexOf('.webp', b[1].length - 5) !== -1) {
return 1
}
if (a[1].indexOf('.webp', a[1].length - 5) !== -1) {
return -1
}
}
return 0
})
let bestSelectedSrc = ''
let tmpOption
for (let i = 0; i < result.length; i++) {
tmpOption = result[i]
bestSelectedSrc = tmpOption[1]
const next = result[i + 1]
if (next && next[0] < containerWidth) {
bestSelectedSrc = tmpOption[1]
break
} else if (!next) {
bestSelectedSrc = tmpOption[1]
break
}
}
return bestSelectedSrc
}
function find (arr, fn) {
let item
for (let i = 0, len = arr.length; i < len; i++) {
if (fn(arr[i])) {
item = arr[i]
break
}
}
return item
}
const getDPR = (scale = 1) => inBrowser ? (window.devicePixelRatio || scale) : scale
function supportWebp () {
if (!inBrowser) return false
let support = true
try {
const elem = document.createElement('canvas')
if (elem.getContext && elem.getContext('2d')) {
support = elem.toDataURL('image/webp').indexOf('data:image/webp') === 0
}
} catch (err) {
support = false
}
return support
}
function throttle (action, delay) {
let timeout = null
let movement = null
let lastRun = 0
let needRun = false
return function () {
needRun = true
if (timeout) {
return
}
let elapsed = Date.now() - lastRun
let context = this
let args = arguments
let runCallback = function () {
lastRun = Date.now()
timeout = false
action.apply(context, args)
}
if (elapsed >= delay) {
runCallback()
} else {
timeout = setTimeout(runCallback, delay)
}
if (needRun) {
clearTimeout(movement)
movement = setTimeout(runCallback, 2 * delay)
}
}
}
function testSupportsPassive () {
if (!inBrowser) return
let support = false
try {
let opts = Object.defineProperty({}, 'passive', {
get: function () {
support = true
}
})
window.addEventListener('test', null, opts)
} catch (e) {}
return support
}
const supportsPassive = testSupportsPassive()
const _ = {
on (el, type, func, capture = false) {
if (supportsPassive) {
el.addEventListener(type, func, {
capture: capture,
passive: true
})
} else {
el.addEventListener(type, func, capture)
}
},
off (el, type, func, capture = false) {
el.removeEventListener(type, func, capture)
}
}
const loadImageAsync = (item, resolve, reject) => {
let image = new Image()
if (!item || !item.src) {
const err = new Error('image src is required')
return reject(err)
}
image.src = item.src
if (item.cors) {
image.crossOrigin = item.cors
}
image.onload = function () {
resolve({
naturalHeight: image.naturalHeight,
naturalWidth: image.naturalWidth,
src: image.src
})
}
image.onerror = function (e) {
reject(e)
}
}
const style = (el, prop) => {
return typeof getComputedStyle !== 'undefined'
? getComputedStyle(el, null).getPropertyValue(prop)
: el.style[prop]
}
const overflow = (el) => {
return style(el, 'overflow') + style(el, 'overflow-y') + style(el, 'overflow-x')
}
const scrollParent = (el) => {
if (!inBrowser) return
if (!(el instanceof HTMLElement)) {
return window
}
let parent = el
while (parent) {
if (parent === document.body || parent === document.documentElement) {
break
}
if (!parent.parentNode) {
break
}
if (/(scroll|auto)/.test(overflow(parent))) {
return parent
}
parent = parent.parentNode
}
return window
}
function isObject (obj) {
return obj !== null && typeof obj === 'object'
}
function ObjectKeys (obj) {
if (!(obj instanceof Object)) return []
if (Object.keys) {
return Object.keys(obj)
} else {
let keys = []
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
keys.push(key)
}
}
return keys
}
}
function ArrayFrom (arrLike) {
let len = arrLike.length
const list = []
for (let i = 0; i < len; i++) {
list.push(arrLike[i])
}
return list
}
function noop () {}
class ImageCache {
constructor ({ max }) {
this.options = {
max: max || 100
}
this._caches = []
}
has (key) {
return this._caches.indexOf(key) > -1
}
add (key) {
if (this.has(key)) return
this._caches.push(key)
if (this._caches.length > this.options.max) {
this.free()
}
}
free () {
this._caches.shift()
}
}
export {
ImageCache,
inBrowser,
CustomEvent,
remove,
some,
find,
assign,
noop,
ArrayFrom,
_,
isObject,
throttle,
supportWebp,
getDPR,
scrollParent,
loadImageAsync,
getBestSelectionFromSrcset,
ObjectKeys
}
================================================
FILE: test/test.spec.js
================================================
import Vue from 'vue'
import VueLazyload from '../src'
import genLazyCore from '../src/lazy'
import assert from 'assert'
describe('VueLazyload.js Test Suite', function () {
it('install', function () {
Vue.use(VueLazyload)
const vm = new Vue()
assert(vm.$Lazyload, 'has $Lazyload')
})
it('_valueFormatter', function () {
const LazyCore = genLazyCore(Vue)
const lazyload = new LazyCore({
error: 'error',
loading: 'loading'
})
expect(lazyload._valueFormatter('src').src).toBe('src')
expect(lazyload._valueFormatter('src').error).toBe('error')
expect(lazyload._valueFormatter('src').loading).toBe('loading')
expect(lazyload._valueFormatter({
src: 'src',
error: 'error',
loading: 'loading'
}).src).toBe('src')
expect(lazyload._valueFormatter({
src: 'src',
error: 'error',
loading: 'loading'
}).error).toBe('error')
expect(lazyload._valueFormatter({
src: 'src',
error: 'error',
loading: 'loading'
}).loading).toBe('loading')
})
})
================================================
FILE: types/index.d.ts
================================================
import "./vue";
import { VueLazyloadPluginObject } from "./lazyload";
declare var VueLazyload: VueLazyloadPluginObject;
export default VueLazyload;
export {
VueLazyloadImage,
VueLazyloadOptions,
VueLazyloadHandler,
VueReactiveListener
} from "./lazyload";
================================================
FILE: types/lazyload.d.ts
================================================
import { PluginObject } from "vue";
interface IntersectionObserverInit {
root?: Element | null;
rootMargin?: string;
threshold?: number | number[];
}
export interface VueLazyloadImage {
src: string;
error?: string;
loading?: string;
}
export interface VueLazyloadOptions {
lazyComponent?: boolean;
preLoad?: number;
error?: string;
loading?: string;
attempt?: number;
listenEvents?: string[];
adapter?: any;
filter?: any;
dispatchEvent?: boolean;
throttleWait?: number;
observer?: boolean;
observerOptions?: IntersectionObserverInit;
silent?: boolean;
preLoadTop?: number;
scale?: number;
hasbind?: boolean;
}
export interface VueReactiveListener {
el: Element;
src: string;
error: string;
loading: string;
bindType: string;
attempt: number;
naturalHeight: number;
naturalWidth: number;
options: VueLazyloadOptions;
rect: DOMRect;
$parent: Element
elRenderer: Function;
performanceData: {
init: number,
loadStart: number,
loadEnd: number
};
}
export interface VueLazyloadListenEvent {
(listener: VueReactiveListener, cache: boolean) : void;
}
export interface VueLazyloadHandler {
$on (event: string, callback: VueLazyloadListenEvent): void;
$once (event: string, callback: VueLazyloadListenEvent): void;
$off (event: string, callback?: VueLazyloadListenEvent): void;
lazyLoadHandler (): void;
}
export interface VueLazyloadPluginObject extends PluginObject {}
================================================
FILE: types/test/index.ts
================================================
import Vue from "vue";
import VueLazyload, { VueLazyloadOptions } from "../index";
Vue.use(VueLazyload);
Vue.use(VueLazyload, {
preLoad: 0,
});
const vm = new Vue({});
vm.$Lazyload.lazyLoadHandler();
vm.$Lazyload.$on('loading', function (state, cache) {
const err: string = state.error;
const el: Element = state.el;
const bol: boolean = cache;
});
================================================
FILE: types/test/tsconfig.json
================================================
{
"compilerOptions": {
"target": "es5",
"module": "es2015",
"moduleResolution": "node",
"lib": [
"es5",
"dom",
"es2015.promise",
"es2015.core"
],
"strict": true,
"noEmit": true
},
"include": [
"*.ts",
"../*.d.ts"
]
}
================================================
FILE: types/vue.d.ts
================================================
/**
* Augment the typings of Vue.js
*/
import Vue from "vue";
import { VueLazyloadHandler } from "./index";
declare module "vue/types/vue" {
interface Vue {
$Lazyload: VueLazyloadHandler;
}
}
================================================
FILE: vue-lazyload.esm.js
================================================
/*!
* Vue-Lazyload.js v1.3.5
* (c) 2023 Awe
* Released under the MIT License.
*/
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var assignSymbols$1 = createCommonjsModule(function (module) {
var toString = Object.prototype.toString;
var isEnumerable = Object.prototype.propertyIsEnumerable;
var getSymbols = Object.getOwnPropertySymbols;
module.exports = function (target) {
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
if (!isObject(target)) {
throw new TypeError('expected the first argument to be an object');
}
if (args.length === 0 || typeof Symbol !== 'function' || typeof getSymbols !== 'function') {
return target;
}
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = args[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var arg = _step.value;
var names = getSymbols(arg);
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = names[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var key = _step2.value;
if (isEnumerable.call(arg, key)) {
target[key] = arg[key];
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return target;
};
function isObject(val) {
return typeof val === 'function' || toString.call(val) === '[object Object]' || Array.isArray(val);
}
});
var assignSymbols$2 = /*#__PURE__*/Object.freeze({
__proto__: null,
'default': assignSymbols$1,
__moduleExports: assignSymbols$1
});
var assignSymbols = ( assignSymbols$2 && assignSymbols$1 ) || assignSymbols$2;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var assignDeep = createCommonjsModule(function (module) {
var toString = Object.prototype.toString;
var isValidKey = function isValidKey(key) {
return key !== '__proto__' && key !== 'constructor' && key !== 'prototype';
};
var assign = module.exports = function (target) {
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
var i = 0;
if (isPrimitive(target)) target = args[i++];
if (!target) target = {};
for (; i < args.length; i++) {
if (isObject(args[i])) {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = Object.keys(args[i])[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var key = _step.value;
if (isValidKey(key)) {
if (isObject(target[key]) && isObject(args[i][key])) {
assign(target[key], args[i][key]);
} else {
target[key] = args[i][key];
}
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
assignSymbols(target, args[i]);
}
}
return target;
};
function isObject(val) {
return typeof val === 'function' || toString.call(val) === '[object Object]';
}
function isPrimitive(val) {
return (typeof val === 'undefined' ? 'undefined' : _typeof(val)) === 'object' ? val === null : typeof val !== 'function';
}
});
var inBrowser = typeof window !== 'undefined' && window !== null;
var hasIntersectionObserver = checkIntersectionObserver();
function checkIntersectionObserver() {
if (inBrowser && 'IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in window.IntersectionObserverEntry.prototype) {
// Minimal polyfill for Edge 15's lack of `isIntersecting`
// See: https://github.com/w3c/IntersectionObserver/issues/211
if (!('isIntersecting' in window.IntersectionObserverEntry.prototype)) {
Object.defineProperty(window.IntersectionObserverEntry.prototype, 'isIntersecting', {
get: function get() {
return this.intersectionRatio > 0;
}
});
}
return true;
}
return false;
}
var modeType = {
event: 'event',
observer: 'observer'
// CustomEvent polyfill for IE
};var CustomEvent = function () {
if (!inBrowser) return;
// not IE
if (typeof window.CustomEvent === 'function') return window.CustomEvent;
function CustomEvent(event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}
CustomEvent.prototype = window.Event.prototype;
return CustomEvent;
}();
function remove(arr, item) {
if (!arr.length) return;
var index = arr.indexOf(item);
if (index > -1) return arr.splice(index, 1);
}
function some(arr, fn) {
var has = false;
for (var i = 0, len = arr.length; i < len; i++) {
if (fn(arr[i])) {
has = true;
break;
}
}
return has;
}
function getBestSelectionFromSrcset(el, scale) {
if (el.tagName !== 'IMG' || !el.getAttribute('data-srcset')) return;
var options = el.getAttribute('data-srcset');
var result = [];
var container = el.parentNode;
var containerWidth = container.offsetWidth * scale;
var spaceIndex = void 0;
var tmpSrc = void 0;
var tmpWidth = void 0;
options = options.trim().split(',');
options.map(function (item) {
item = item.trim();
spaceIndex = item.lastIndexOf(' ');
if (spaceIndex === -1) {
tmpSrc = item;
tmpWidth = 999998;
} else {
tmpSrc = item.substr(0, spaceIndex);
tmpWidth = parseInt(item.substr(spaceIndex + 1, item.length - spaceIndex - 2), 10);
}
result.push([tmpWidth, tmpSrc]);
});
result.sort(function (a, b) {
if (a[0] < b[0]) {
return 1;
}
if (a[0] > b[0]) {
return -1;
}
if (a[0] === b[0]) {
if (b[1].indexOf('.webp', b[1].length - 5) !== -1) {
return 1;
}
if (a[1].indexOf('.webp', a[1].length - 5) !== -1) {
return -1;
}
}
return 0;
});
var bestSelectedSrc = '';
var tmpOption = void 0;
for (var i = 0; i < result.length; i++) {
tmpOption = result[i];
bestSelectedSrc = tmpOption[1];
var next = result[i + 1];
if (next && next[0] < containerWidth) {
bestSelectedSrc = tmpOption[1];
break;
} else if (!next) {
bestSelectedSrc = tmpOption[1];
break;
}
}
return bestSelectedSrc;
}
function find(arr, fn) {
var item = void 0;
for (var i = 0, len = arr.length; i < len; i++) {
if (fn(arr[i])) {
item = arr[i];
break;
}
}
return item;
}
var getDPR = function getDPR() {
var scale = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
return inBrowser ? window.devicePixelRatio || scale : scale;
};
function supportWebp() {
if (!inBrowser) return false;
var support = true;
try {
var elem = document.createElement('canvas');
if (elem.getContext && elem.getContext('2d')) {
support = elem.toDataURL('image/webp').indexOf('data:image/webp') === 0;
}
} catch (err) {
support = false;
}
return support;
}
function throttle(action, delay) {
var timeout = null;
var movement = null;
var lastRun = 0;
var needRun = false;
return function () {
needRun = true;
if (timeout) {
return;
}
var elapsed = Date.now() - lastRun;
var context = this;
var args = arguments;
var runCallback = function runCallback() {
lastRun = Date.now();
timeout = false;
action.apply(context, args);
};
if (elapsed >= delay) {
runCallback();
} else {
timeout = setTimeout(runCallback, delay);
}
if (needRun) {
clearTimeout(movement);
movement = setTimeout(runCallback, 2 * delay);
}
};
}
function testSupportsPassive() {
if (!inBrowser) return;
var support = false;
try {
var opts = Object.defineProperty({}, 'passive', {
get: function get() {
support = true;
}
});
window.addEventListener('test', null, opts);
} catch (e) {}
return support;
}
var supportsPassive = testSupportsPassive();
var _ = {
on: function on(el, type, func) {
var capture = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
if (supportsPassive) {
el.addEventListener(type, func, {
capture: capture,
passive: true
});
} else {
el.addEventListener(type, func, capture);
}
},
off: function off(el, type, func) {
var capture = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
el.removeEventListener(type, func, capture);
}
};
var loadImageAsync = function loadImageAsync(item, resolve, reject) {
var image = new Image();
if (!item || !item.src) {
var err = new Error('image src is required');
return reject(err);
}
image.src = item.src;
if (item.cors) {
image.crossOrigin = item.cors;
}
image.onload = function () {
resolve({
naturalHeight: image.naturalHeight,
naturalWidth: image.naturalWidth,
src: image.src
});
};
image.onerror = function (e) {
reject(e);
};
};
var style = function style(el, prop) {
return typeof getComputedStyle !== 'undefined' ? getComputedStyle(el, null).getPropertyValue(prop) : el.style[prop];
};
var overflow = function overflow(el) {
return style(el, 'overflow') + style(el, 'overflow-y') + style(el, 'overflow-x');
};
var scrollParent = function scrollParent(el) {
if (!inBrowser) return;
if (!(el instanceof HTMLElement)) {
return window;
}
var parent = el;
while (parent) {
if (parent === document.body || parent === document.documentElement) {
break;
}
if (!parent.parentNode) {
break;
}
if (/(scroll|auto)/.test(overflow(parent))) {
return parent;
}
parent = parent.parentNode;
}
return window;
};
function isObject(obj) {
return obj !== null && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object';
}
function ObjectKeys(obj) {
if (!(obj instanceof Object)) return [];
if (Object.keys) {
return Object.keys(obj);
} else {
var keys = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
keys.push(key);
}
}
return keys;
}
}
function ArrayFrom(arrLike) {
var len = arrLike.length;
var list = [];
for (var i = 0; i < len; i++) {
list.push(arrLike[i]);
}
return list;
}
function noop() {}
var ImageCache = function () {
function ImageCache(_ref) {
var max = _ref.max;
classCallCheck(this, ImageCache);
this.options = {
max: max || 100
};
this._caches = [];
}
createClass(ImageCache, [{
key: 'has',
value: function has(key) {
return this._caches.indexOf(key) > -1;
}
}, {
key: 'add',
value: function add(key) {
if (this.has(key)) return;
this._caches.push(key);
if (this._caches.length > this.options.max) {
this.free();
}
}
}, {
key: 'free',
value: function free() {
this._caches.shift();
}
}]);
return ImageCache;
}();
// el: {
// state,
// src,
// error,
// loading
// }
var ReactiveListener = function () {
function ReactiveListener(_ref) {
var el = _ref.el,
src = _ref.src,
error = _ref.error,
loading = _ref.loading,
bindType = _ref.bindType,
$parent = _ref.$parent,
options = _ref.options,
cors = _ref.cors,
elRenderer = _ref.elRenderer,
imageCache = _ref.imageCache;
classCallCheck(this, ReactiveListener);
this.el = el;
this.src = src;
this.error = error;
this.loading = loading;
this.bindType = bindType;
this.attempt = 0;
this.cors = cors;
this.naturalHeight = 0;
this.naturalWidth = 0;
this.options = options;
this.rect = null;
this.$parent = $parent;
this.elRenderer = elRenderer;
this._imageCache = imageCache;
this.performanceData = {
init: Date.now(),
loadStart: 0,
loadEnd: 0
};
this.filter();
this.initState();
this.render('loading', false);
}
/*
* init listener state
* @return
*/
createClass(ReactiveListener, [{
key: 'initState',
value: function initState() {
if ('dataset' in this.el) {
this.el.dataset.src = this.src;
} else {
this.el.setAttribute('data-src', this.src);
}
this.state = {
loading: false,
error: false,
loaded: false,
rendered: false
};
}
/*
* record performance
* @return
*/
}, {
key: 'record',
value: function record(event) {
this.performanceData[event] = Date.now();
}
/*
* update image listener data
* @param {String} image uri
* @param {String} loading image uri
* @param {String} error image uri
* @return
*/
}, {
key: 'update',
value: function update(_ref2) {
var src = _ref2.src,
loading = _ref2.loading,
error = _ref2.error;
var oldSrc = this.src;
this.src = src;
this.loading = loading;
this.error = error;
this.filter();
if (oldSrc !== this.src) {
this.attempt = 0;
this.initState();
}
}
/*
* get el node rect
* @return
*/
}, {
key: 'getRect',
value: function getRect() {
this.rect = this.el.getBoundingClientRect();
}
/*
* check el is in view
* @return {Boolean} el is in view
*/
}, {
key: 'checkInView',
value: function checkInView() {
this.getRect();
return this.rect.top < window.innerHeight * this.options.preLoad && this.rect.bottom > this.options.preLoadTop && this.rect.left < window.innerWidth * this.options.preLoad && this.rect.right > 0;
}
/*
* listener filter
*/
}, {
key: 'filter',
value: function filter() {
var _this = this;
ObjectKeys(this.options.filter).map(function (key) {
_this.options.filter[key](_this, _this.options);
});
}
/*
* render loading first
* @params cb:Function
* @return
*/
}, {
key: 'renderLoading',
value: function renderLoading(cb) {
var _this2 = this;
this.state.loading = true;
loadImageAsync({
src: this.loading,
cors: this.cors
}, function (data) {
_this2.render('loading', false);
_this2.state.loading = false;
cb();
}, function () {
// handler `loading image` load failed
cb();
_this2.state.loading = false;
if (!_this2.options.silent) console.warn('VueLazyload log: load failed with loading image(' + _this2.loading + ')');
});
}
/*
* try load image and render it
* @return
*/
}, {
key: 'load',
value: function load() {
var _this3 = this;
var onFinish = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : noop;
if (this.attempt > this.options.attempt - 1 && this.state.error) {
if (!this.options.silent) console.log('VueLazyload log: ' + this.src + ' tried too more than ' + this.options.attempt + ' times');
onFinish();
return;
}
if (this.state.rendered && this.state.loaded) return;
if (this._imageCache.has(this.src)) {
this.state.loaded = true;
this.render('loaded', true);
this.state.rendered = true;
return onFinish();
}
this.renderLoading(function () {
_this3.attempt++;
_this3.options.adapter['beforeLoad'] && _this3.options.adapter['beforeLoad'](_this3, _this3.options);
_this3.record('loadStart');
loadImageAsync({
src: _this3.src,
cors: _this3.cors
}, function (data) {
_this3.naturalHeight = data.naturalHeight;
_this3.naturalWidth = data.naturalWidth;
_this3.state.loaded = true;
_this3.state.error = false;
_this3.record('loadEnd');
_this3.render('loaded', false);
_this3.state.rendered = true;
_this3._imageCache.add(_this3.src);
onFinish();
}, function (err) {
!_this3.options.silent && console.error(err);
_this3.state.error = true;
_this3.state.loaded = false;
_this3.render('error', false);
});
});
}
/*
* render image
* @param {String} state to render // ['loading', 'src', 'error']
* @param {String} is form cache
* @return
*/
}, {
key: 'render',
value: function render(state, cache) {
this.elRenderer(this, state, cache);
}
/*
* output performance data
* @return {Object} performance data
*/
}, {
key: 'performance',
value: function performance() {
var state = 'loading';
var time = 0;
if (this.state.loaded) {
state = 'loaded';
time = (this.performanceData.loadEnd - this.performanceData.loadStart) / 1000;
}
if (this.state.error) state = 'error';
return {
src: this.src,
state: state,
time: time
};
}
/*
* $destroy
* @return
*/
}, {
key: '$destroy',
value: function $destroy() {
this.el = null;
this.src = null;
this.error = null;
this.loading = null;
this.bindType = null;
this.attempt = 0;
}
}]);
return ReactiveListener;
}();
var DEFAULT_URL = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
var DEFAULT_EVENTS = ['scroll', 'wheel', 'mousewheel', 'resize', 'animationend', 'transitionend', 'touchmove'];
var DEFAULT_OBSERVER_OPTIONS = {
rootMargin: '0px',
threshold: 0
};
function Lazy(Vue) {
return function () {
function Lazy(_ref) {
var preLoad = _ref.preLoad,
error = _ref.error,
throttleWait = _ref.throttleWait,
preLoadTop = _ref.preLoadTop,
dispatchEvent = _ref.dispatchEvent,
loading = _ref.loading,
attempt = _ref.attempt,
_ref$silent = _ref.silent,
silent = _ref$silent === undefined ? true : _ref$silent,
scale = _ref.scale,
listenEvents = _ref.listenEvents;
_ref.hasbind;
var filter = _ref.filter,
adapter = _ref.adapter,
observer = _ref.observer,
observerOptions = _ref.observerOptions;
classCallCheck(this, Lazy);
this.version = '"1.3.5"';
this.mode = modeType.event;
this.ListenerQueue = [];
this.TargetIndex = 0;
this.TargetQueue = [];
this.options = {
silent: silent,
dispatchEvent: !!dispatchEvent,
throttleWait: throttleWait || 200,
preLoad: preLoad || 1.3,
preLoadTop: preLoadTop || 0,
error: error || DEFAULT_URL,
loading: loading || DEFAULT_URL,
attempt: attempt || 3,
scale: scale || getDPR(scale),
ListenEvents: listenEvents || DEFAULT_EVENTS,
hasbind: false,
supportWebp: supportWebp(),
filter: filter || {},
adapter: adapter || {},
observer: !!observer,
observerOptions: observerOptions || DEFAULT_OBSERVER_OPTIONS
};
this._initEvent();
this._imageCache = new ImageCache({ max: 200 });
this.lazyLoadHandler = throttle(this._lazyLoadHandler.bind(this), this.options.throttleWait);
this.setMode(this.options.observer ? modeType.observer : modeType.event);
}
/**
* update config
* @param {Object} config params
* @return
*/
createClass(Lazy, [{
key: 'config',
value: function config() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
assignDeep(this.options, options);
}
/**
* output listener's load performance
* @return {Array}
*/
}, {
key: 'performance',
value: function performance() {
var list = [];
this.ListenerQueue.map(function (item) {
list.push(item.performance());
});
return list;
}
/*
* add lazy component to queue
* @param {Vue} vm lazy component instance
* @return
*/
}, {
key: 'addLazyBox',
value: function addLazyBox(vm) {
this.ListenerQueue.push(vm);
if (inBrowser) {
this._addListenerTarget(window);
this._observer && this._observer.observe(vm.el);
if (vm.$el && vm.$el.parentNode) {
this._addListenerTarget(vm.$el.parentNode);
}
}
}
/*
* add image listener to queue
* @param {DOM} el
* @param {object} binding vue directive binding
* @param {vnode} vnode vue directive vnode
* @return
*/
}, {
key: 'add',
value: function add(el, binding, vnode) {
var _this = this;
if (some(this.ListenerQueue, function (item) {
return item.el === el;
})) {
this.update(el, binding);
return Vue.nextTick(this.lazyLoadHandler);
}
var _valueFormatter2 = this._valueFormatter(binding.value),
src = _valueFormatter2.src,
loading = _valueFormatter2.loading,
error = _valueFormatter2.error,
cors = _valueFormatter2.cors;
Vue.nextTick(function () {
src = getBestSelectionFromSrcset(el, _this.options.scale) || src;
_this._observer && _this._observer.observe(el);
var container = Object.keys(binding.modifiers)[0];
var $parent = void 0;
if (container) {
$parent = vnode.context.$refs[container];
// if there is container passed in, try ref first, then fallback to getElementById to support the original usage
$parent = $parent ? $parent.$el || $parent : document.getElementById(container);
}
if (!$parent) {
$parent = scrollParent(el);
}
var newListener = new ReactiveListener({
bindType: binding.arg,
$parent: $parent,
el: el,
loading: loading,
error: error,
src: src,
cors: cors,
elRenderer: _this._elRenderer.bind(_this),
options: _this.options,
imageCache: _this._imageCache
});
_this.ListenerQueue.push(newListener);
if (inBrowser) {
_this._addListenerTarget(window);
_this._addListenerTarget($parent);
}
_this.lazyLoadHandler();
Vue.nextTick(function () {
return _this.lazyLoadHandler();
});
});
}
/**
* update image src
* @param {DOM} el
* @param {object} vue directive binding
* @return
*/
}, {
key: 'update',
value: function update(el, binding, vnode) {
var _this2 = this;
var _valueFormatter3 = this._valueFormatter(binding.value),
src = _valueFormatter3.src,
loading = _valueFormatter3.loading,
error = _valueFormatter3.error;
src = getBestSelectionFromSrcset(el, this.options.scale) || src;
var exist = find(this.ListenerQueue, function (item) {
return item.el === el;
});
if (!exist) {
this.add(el, binding, vnode);
} else {
exist.update({
src: src,
loading: loading,
error: error
});
}
if (this._observer) {
this._observer.unobserve(el);
this._observer.observe(el);
}
this.lazyLoadHandler();
Vue.nextTick(function () {
return _this2.lazyLoadHandler();
});
}
/**
* remove listener form list
* @param {DOM} el
* @return
*/
}, {
key: 'remove',
value: function remove$1(el) {
if (!el) return;
this._observer && this._observer.unobserve(el);
var existItem = find(this.ListenerQueue, function (item) {
return item.el === el;
});
if (existItem) {
this._removeListenerTarget(existItem.$parent);
this._removeListenerTarget(window);
remove(this.ListenerQueue, existItem);
existItem.$destroy();
}
}
/*
* remove lazy components form list
* @param {Vue} vm Vue instance
* @return
*/
}, {
key: 'removeComponent',
value: function removeComponent(vm) {
if (!vm) return;
remove(this.ListenerQueue, vm);
this._observer && this._observer.unobserve(vm.el);
if (vm.$parent && vm.$el.parentNode) {
this._removeListenerTarget(vm.$el.parentNode);
}
this._removeListenerTarget(window);
}
}, {
key: 'setMode',
value: function setMode(mode) {
var _this3 = this;
if (!hasIntersectionObserver && mode === modeType.observer) {
mode = modeType.event;
}
this.mode = mode; // event or observer
if (mode === modeType.event) {
if (this._observer) {
this.ListenerQueue.forEach(function (listener) {
_this3._observer.unobserve(listener.el);
});
this._observer = null;
}
this.TargetQueue.forEach(function (target) {
_this3._initListen(target.el, true);
});
} else {
this.TargetQueue.forEach(function (target) {
_this3._initListen(target.el, false);
});
this._initIntersectionObserver();
}
}
/*
*** Private functions ***
*/
/*
* add listener target
* @param {DOM} el listener target
* @return
*/
}, {
key: '_addListenerTarget',
value: function _addListenerTarget(el) {
if (!el) return;
var target = find(this.TargetQueue, function (target) {
return target.el === el;
});
if (!target) {
target = {
el: el,
id: ++this.TargetIndex,
childrenCount: 1,
listened: true
};
this.mode === modeType.event && this._initListen(target.el, true);
this.TargetQueue.push(target);
} else {
target.childrenCount++;
}
return this.TargetIndex;
}
/*
* remove listener target or reduce target childrenCount
* @param {DOM} el or window
* @return
*/
}, {
key: '_removeListenerTarget',
value: function _removeListenerTarget(el) {
var _this4 = this;
this.TargetQueue.forEach(function (target, index) {
if (target.el === el) {
target.childrenCount--;
if (!target.childrenCount) {
_this4._initListen(target.el, false);
_this4.TargetQueue.splice(index, 1);
target = null;
}
}
});
}
/*
* add or remove eventlistener
* @param {DOM} el DOM or Window
* @param {boolean} start flag
* @return
*/
}, {
key: '_initListen',
value: function _initListen(el, start) {
var _this5 = this;
this.options.ListenEvents.forEach(function (evt) {
return _[start ? 'on' : 'off'](el, evt, _this5.lazyLoadHandler);
});
}
}, {
key: '_initEvent',
value: function _initEvent() {
var _this6 = this;
this.Event = {
listeners: {
loading: [],
loaded: [],
error: []
}
};
this.$on = function (event, func) {
if (!_this6.Event.listeners[event]) _this6.Event.listeners[event] = [];
_this6.Event.listeners[event].push(func);
};
this.$once = function (event, func) {
var vm = _this6;
function on() {
vm.$off(event, on);
func.apply(vm, arguments);
}
_this6.$on(event, on);
};
this.$off = function (event, func) {
if (!func) {
if (!_this6.Event.listeners[event]) return;
_this6.Event.listeners[event].length = 0;
return;
}
remove(_this6.Event.listeners[event], func);
};
this.$emit = function (event, context, inCache) {
if (!_this6.Event.listeners[event]) return;
_this6.Event.listeners[event].forEach(function (func) {
return func(context, inCache);
});
};
}
/**
* find nodes which in viewport and trigger load
* @return
*/
}, {
key: '_lazyLoadHandler',
value: function _lazyLoadHandler() {
var _this7 = this;
var freeList = [];
this.ListenerQueue.forEach(function (listener, index) {
if (!listener.el || !listener.el.parentNode) {
freeList.push(listener);
}
var catIn = listener.checkInView();
if (!catIn) return;
listener.load();
});
freeList.forEach(function (item) {
remove(_this7.ListenerQueue, item);
item.$destroy();
});
}
/**
* init IntersectionObserver
* set mode to observer
* @return
*/
}, {
key: '_initIntersectionObserver',
value: function _initIntersectionObserver() {
var _this8 = this;
if (!hasIntersectionObserver) return;
this._observer = new IntersectionObserver(this._observerHandler.bind(this), this.options.observerOptions);
if (this.ListenerQueue.length) {
this.ListenerQueue.forEach(function (listener) {
_this8._observer.observe(listener.el);
});
}
}
/**
* init IntersectionObserver
* @return
*/
}, {
key: '_observerHandler',
value: function _observerHandler(entries, observer) {
var _this9 = this;
entries.forEach(function (entry) {
if (entry.isIntersecting) {
_this9.ListenerQueue.forEach(function (listener) {
if (listener.el === entry.target) {
if (listener.state.loaded) return _this9._observer.unobserve(listener.el);
listener.load();
}
});
}
});
}
/**
* set element attribute with image'url and state
* @param {object} lazyload listener object
* @param {string} state will be rendered
* @param {bool} inCache is rendered from cache
* @return
*/
}, {
key: '_elRenderer',
value: function _elRenderer(listener, state, cache) {
if (!listener.el) return;
var el = listener.el,
bindType = listener.bindType;
var src = void 0;
switch (state) {
case 'loading':
src = listener.loading;
break;
case 'error':
src = listener.error;
break;
default:
src = listener.src;
break;
}
if (bindType) {
el.style[bindType] = 'url("' + src + '")';
} else if (el.getAttribute('src') !== src) {
el.setAttribute('src', src);
}
el.setAttribute('lazy', state);
this.$emit(state, listener, cache);
this.options.adapter[state] && this.options.adapter[state](listener, this.options);
if (this.options.dispatchEvent) {
var event = new CustomEvent(state, {
detail: listener
});
el.dispatchEvent(event);
}
}
/**
* generate loading loaded error image url
* @param {string} image's src
* @return {object} image's loading, loaded, error url
*/
}, {
key: '_valueFormatter',
value: function _valueFormatter(value) {
var src = value;
var loading = this.options.loading;
var error = this.options.error;
// value is object
if (isObject(value)) {
if (!value.src && !this.options.silent) console.error('Vue Lazyload warning: miss src with ' + value);
src = value.src;
loading = value.loading || this.options.loading;
error = value.error || this.options.error;
}
return {
src: src,
loading: loading,
error: error
};
}
}]);
return Lazy;
}();
}
Lazy.install = function (Vue) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var LazyClass = Lazy(Vue);
var lazy = new LazyClass(options);
var isVue2 = Vue.version.split('.')[0] === '2';
if (isVue2) {
Vue.directive('lazy', {
bind: lazy.add.bind(lazy),
update: lazy.update.bind(lazy),
componentUpdated: lazy.lazyLoadHandler.bind(lazy),
unbind: lazy.remove.bind(lazy)
});
} else {
Vue.directive('lazy', {
bind: lazy.lazyLoadHandler.bind(lazy),
update: function update(newValue, oldValue) {
assignDeep(this.vm.$refs, this.vm.$els);
lazy.add(this.el, {
modifiers: this.modifiers || {},
arg: this.arg,
value: newValue,
oldValue: oldValue
}, {
context: this.vm
});
},
unbind: function unbind() {
lazy.remove(this.el);
}
});
}
};
var LazyComponent = function LazyComponent(lazy) {
return {
props: {
tag: {
type: String,
default: 'div'
}
},
render: function render(h) {
return h(this.tag, null, this.show ? this.$slots.default : null);
},
data: function data() {
return {
el: null,
state: {
loaded: false
},
rect: {},
show: false
};
},
mounted: function mounted() {
this.el = this.$el;
lazy.addLazyBox(this);
lazy.lazyLoadHandler();
},
beforeDestroy: function beforeDestroy() {
lazy.removeComponent(this);
},
methods: {
getRect: function getRect() {
this.rect = this.$el.getBoundingClientRect();
},
checkInView: function checkInView() {
this.getRect();
return inBrowser && this.rect.top < window.innerHeight * lazy.options.preLoad && this.rect.bottom > 0 && this.rect.left < window.innerWidth * lazy.options.preLoad && this.rect.right > 0;
},
load: function load() {
this.show = true;
this.state.loaded = true;
this.$emit('show', this);
},
destroy: function destroy() {
return this.$destroy;
}
}
};
};
LazyComponent.install = function (Vue) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var LazyClass = Lazy(Vue);
var lazy = new LazyClass(options);
Vue.component('lazy-component', LazyComponent(lazy));
};
var LazyContainerMananger = function () {
function LazyContainerMananger(_ref) {
var lazy = _ref.lazy;
classCallCheck(this, LazyContainerMananger);
this.lazy = lazy;
lazy.lazyContainerMananger = this;
this._queue = [];
}
createClass(LazyContainerMananger, [{
key: 'bind',
value: function bind(el, binding, vnode) {
var container = new LazyContainer({ el: el, binding: binding, vnode: vnode, lazy: this.lazy });
this._queue.push(container);
}
}, {
key: 'update',
value: function update(el, binding, vnode) {
var container = find(this._queue, function (item) {
return item.el === el;
});
if (!container) return;
container.update({ el: el, binding: binding, vnode: vnode });
}
}, {
key: 'unbind',
value: function unbind(el, binding, vnode) {
var container = find(this._queue, function (item) {
return item.el === el;
});
if (!container) return;
container.clear();
remove(this._queue, container);
}
}]);
return LazyContainerMananger;
}();
var defaultOptions = {
selector: 'img'
};
var LazyContainer = function () {
function LazyContainer(_ref2) {
var el = _ref2.el,
binding = _ref2.binding,
vnode = _ref2.vnode,
lazy = _ref2.lazy;
classCallCheck(this, LazyContainer);
this.el = null;
this.vnode = vnode;
this.binding = binding;
this.options = {};
this.lazy = lazy;
this._queue = [];
this.update({ el: el, binding: binding });
}
createClass(LazyContainer, [{
key: 'update',
value: function update(_ref3) {
var _this = this;
var el = _ref3.el,
binding = _ref3.binding;
this.el = el;
this.options = assignDeep({}, defaultOptions, binding.value);
var imgs = this.getImgs();
imgs.forEach(function (el) {
_this.lazy.add(el, assignDeep({}, _this.binding, {
value: {
src: 'dataset' in el ? el.dataset.src : el.getAttribute('data-src'),
error: ('dataset' in el ? el.dataset.error : el.getAttribute('data-error')) || _this.options.error,
loading: ('dataset' in el ? el.dataset.loading : el.getAttribute('data-loading')) || _this.options.loading
}
}), _this.vnode);
});
}
}, {
key: 'getImgs',
value: function getImgs() {
return ArrayFrom(this.el.querySelectorAll(this.options.selector));
}
}, {
key: 'clear',
value: function clear() {
var _this2 = this;
var imgs = this.getImgs();
imgs.forEach(function (el) {
return _this2.lazy.remove(el);
});
this.vnode = null;
this.binding = null;
this.lazy = null;
}
}]);
return LazyContainer;
}();
LazyContainer.install = function (Vue) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var LazyClass = Lazy(Vue);
var lazy = new LazyClass(options);
var lazyContainer = new LazyContainer({ lazy: lazy });
var isVue2 = Vue.version.split('.')[0] === '2';
if (isVue2) {
Vue.directive('lazy-container', {
bind: lazyContainer.bind.bind(lazyContainer),
componentUpdated: lazyContainer.update.bind(lazyContainer),
unbind: lazyContainer.unbind.bind(lazyContainer)
});
} else {
Vue.directive('lazy-container', {
update: function update(newValue, oldValue) {
lazyContainer.update(this.el, {
modifiers: this.modifiers || {},
arg: this.arg,
value: newValue,
oldValue: oldValue
}, {
context: this.vm
});
},
unbind: function unbind() {
lazyContainer.unbind(this.el);
}
});
}
};
var LazyImage = function LazyImage(lazyManager) {
return {
props: {
src: [String, Object],
tag: {
type: String,
default: 'img'
}
},
render: function render(h) {
return h(this.tag, {
attrs: {
src: this.renderSrc
}
}, this.$slots.default);
},
data: function data() {
return {
el: null,
options: {
src: '',
error: '',
loading: '',
attempt: lazyManager.options.attempt
},
state: {
loaded: false,
error: false,
attempt: 0
},
rect: {},
renderSrc: ''
};
},
watch: {
src: function src() {
this.init();
lazyManager.addLazyBox(this);
lazyManager.lazyLoadHandler();
}
},
created: function created() {
this.init();
this.renderSrc = this.options.loading;
},
mounted: function mounted() {
this.el = this.$el;
lazyManager.addLazyBox(this);
lazyManager.lazyLoadHandler();
},
beforeDestroy: function beforeDestroy() {
lazyManager.removeComponent(this);
},
methods: {
init: function init() {
var _lazyManager$_valueFo = lazyManager._valueFormatter(this.src),
src = _lazyManager$_valueFo.src,
loading = _lazyManager$_valueFo.loading,
error = _lazyManager$_valueFo.error;
this.state.loaded = false;
this.options.src = src;
this.options.error = error;
this.options.loading = loading;
this.renderSrc = this.options.loading;
},
getRect: function getRect() {
this.rect = this.$el.getBoundingClientRect();
},
checkInView: function checkInView() {
this.getRect();
return inBrowser && this.rect.top < window.innerHeight * lazyManager.options.preLoad && this.rect.bottom > 0 && this.rect.left < window.innerWidth * lazyManager.options.preLoad && this.rect.right > 0;
},
load: function load() {
var _this = this;
var onFinish = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : noop;
if (this.state.attempt > this.options.attempt - 1 && this.state.error) {
if (!lazyManager.options.silent) console.log('VueLazyload log: ' + this.options.src + ' tried too more than ' + this.options.attempt + ' times');
onFinish();
return;
}
var src = this.options.src;
loadImageAsync({ src: src }, function (_ref) {
var src = _ref.src;
_this.renderSrc = src;
_this.state.loaded = true;
}, function (e) {
_this.state.attempt++;
_this.renderSrc = _this.options.error;
_this.state.error = true;
});
}
}
};
};
LazyImage.install = function (Vue) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var LazyClass = Lazy(Vue);
var lazy = new LazyClass(options);
Vue.component('lazy-image', LazyImage(lazy));
};
var index = {
/*
* install function
* @param {Vue} Vue
* @param {object} options lazyload options
*/
install: function install(Vue) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var LazyClass = Lazy(Vue);
var lazy = new LazyClass(options);
var lazyContainer = new LazyContainerMananger({ lazy: lazy });
var isVue2 = Vue.version.split('.')[0] === '2';
Vue.prototype.$Lazyload = lazy;
if (options.lazyComponent) {
Vue.component('lazy-component', LazyComponent(lazy));
}
if (options.lazyImage) {
Vue.component('lazy-image', LazyImage(lazy));
}
if (isVue2) {
Vue.directive('lazy', {
bind: lazy.add.bind(lazy),
update: lazy.update.bind(lazy),
componentUpdated: lazy.lazyLoadHandler.bind(lazy),
unbind: lazy.remove.bind(lazy)
});
Vue.directive('lazy-container', {
bind: lazyContainer.bind.bind(lazyContainer),
componentUpdated: lazyContainer.update.bind(lazyContainer),
unbind: lazyContainer.unbind.bind(lazyContainer)
});
} else {
Vue.directive('lazy', {
bind: lazy.lazyLoadHandler.bind(lazy),
update: function update(newValue, oldValue) {
assignDeep(this.vm.$refs, this.vm.$els);
lazy.add(this.el, {
modifiers: this.modifiers || {},
arg: this.arg,
value: newValue,
oldValue: oldValue
}, {
context: this.vm
});
},
unbind: function unbind() {
lazy.remove(this.el);
}
});
Vue.directive('lazy-container', {
update: function update(newValue, oldValue) {
lazyContainer.update(this.el, {
modifiers: this.modifiers || {},
arg: this.arg,
value: newValue,
oldValue: oldValue
}, {
context: this.vm
});
},
unbind: function unbind() {
lazyContainer.unbind(this.el);
}
});
}
}
};
export { Lazy, LazyComponent, LazyContainerMananger as LazyContainer, LazyImage, index as default };
================================================
FILE: vue-lazyload.js
================================================
/*!
* Vue-Lazyload.js v1.3.5
* (c) 2023 Awe
* Released under the MIT License.
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).VueLazyload={})}(this,(function(t){"use strict";function e(t,e){return t(e={exports:{}},e.exports),e.exports}var n=e((function(t){var e=Object.prototype.toString,n=Object.prototype.propertyIsEnumerable,i=Object.getOwnPropertySymbols;t.exports=function(t){for(var r=arguments.length,o=Array(r>1?r-1:0),s=1;s1?e-1:0),u=1;u0}}),!0;return!1}();var c="event",h="observer",f=function(){if(l)return"function"==typeof window.CustomEvent?window.CustomEvent:(t.prototype=window.Event.prototype,t);function t(t,e){e=e||{bubbles:!1,cancelable:!1,detail:void 0};var n=document.createEvent("CustomEvent");return n.initCustomEvent(t,e.bubbles,e.cancelable,e.detail),n}}();function v(t,e){if(t.length){var n=t.indexOf(e);return n>-1?t.splice(n,1):void 0}}function p(t,e){if("IMG"===t.tagName&&t.getAttribute("data-srcset")){var n=t.getAttribute("data-srcset"),i=[],r=t.parentNode.offsetWidth*e,o=void 0,s=void 0,a=void 0;(n=n.trim().split(",")).map((function(t){t=t.trim(),-1===(o=t.lastIndexOf(" "))?(s=t,a=999998):(s=t.substr(0,o),a=parseInt(t.substr(o+1,t.length-o-2),10)),i.push([a,s])})),i.sort((function(t,e){if(t[0]e[0])return-1;if(t[0]===e[0]){if(-1!==e[1].indexOf(".webp",e[1].length-5))return 1;if(-1!==t[1].indexOf(".webp",t[1].length-5))return-1}return 0}));for(var u="",l=void 0,d=0;d0&&void 0!==arguments[0]?arguments[0]:1;return l&&window.devicePixelRatio||t};function b(){if(!l)return!1;var t=!0;try{var e=document.createElement("canvas");e.getContext&&e.getContext("2d")&&(t=0===e.toDataURL("image/webp").indexOf("data:image/webp"))}catch(e){t=!1}return t}var m=function(){if(l){var t=!1;try{var e=Object.defineProperty({},"passive",{get:function(){t=!0}});window.addEventListener("test",null,e)}catch(t){}return t}}(),w={on:function(t,e,n){var i=arguments.length>3&&void 0!==arguments[3]&&arguments[3];m?t.addEventListener(e,n,{capture:i,passive:!0}):t.addEventListener(e,n,i)},off:function(t,e,n){var i=arguments.length>3&&void 0!==arguments[3]&&arguments[3];t.removeEventListener(e,n,i)}},L=function(t,e,n){var i=new Image;if(!t||!t.src){var r=new Error("image src is required");return n(r)}i.src=t.src,t.cors&&(i.crossOrigin=t.cors),i.onload=function(){e({naturalHeight:i.naturalHeight,naturalWidth:i.naturalWidth,src:i.src})},i.onerror=function(t){n(t)}},_=function(t,e){return"undefined"!=typeof getComputedStyle?getComputedStyle(t,null).getPropertyValue(e):t.style[e]},z=function(t){return _(t,"overflow")+_(t,"overflow-y")+_(t,"overflow-x")};function E(){}var k=function(){function t(e){var n=e.max;s(this,t),this.options={max:n||100},this._caches=[]}return a(t,[{key:"has",value:function(t){return this._caches.indexOf(t)>-1}},{key:"add",value:function(t){this.has(t)||(this._caches.push(t),this._caches.length>this.options.max&&this.free())}},{key:"free",value:function(){this._caches.shift()}}]),t}(),x=function(){function t(e){var n=e.el,i=e.src,r=e.error,o=e.loading,a=e.bindType,u=e.$parent,l=e.options,d=e.cors,c=e.elRenderer,h=e.imageCache;s(this,t),this.el=n,this.src=i,this.error=r,this.loading=o,this.bindType=a,this.attempt=0,this.cors=d,this.naturalHeight=0,this.naturalWidth=0,this.options=l,this.rect=null,this.$parent=u,this.elRenderer=c,this._imageCache=h,this.performanceData={init:Date.now(),loadStart:0,loadEnd:0},this.filter(),this.initState(),this.render("loading",!1)}return a(t,[{key:"initState",value:function(){"dataset"in this.el?this.el.dataset.src=this.src:this.el.setAttribute("data-src",this.src),this.state={loading:!1,error:!1,loaded:!1,rendered:!1}}},{key:"record",value:function(t){this.performanceData[t]=Date.now()}},{key:"update",value:function(t){var e=t.src,n=t.loading,i=t.error,r=this.src;this.src=e,this.loading=n,this.error=i,this.filter(),r!==this.src&&(this.attempt=0,this.initState())}},{key:"getRect",value:function(){this.rect=this.el.getBoundingClientRect()}},{key:"checkInView",value:function(){return this.getRect(),this.rect.topthis.options.preLoadTop&&this.rect.left0}},{key:"filter",value:function(){var t=this;(function(t){if(!(t instanceof Object))return[];if(Object.keys)return Object.keys(t);var e=[];for(var n in t)t.hasOwnProperty(n)&&e.push(n);return e})(this.options.filter).map((function(e){t.options.filter[e](t,t.options)}))}},{key:"renderLoading",value:function(t){var e=this;this.state.loading=!0,L({src:this.loading,cors:this.cors},(function(n){e.render("loading",!1),e.state.loading=!1,t()}),(function(){t(),e.state.loading=!1,e.options.silent||console.warn("VueLazyload log: load failed with loading image("+e.loading+")")}))}},{key:"load",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:E;return this.attempt>this.options.attempt-1&&this.state.error?(this.options.silent||console.log("VueLazyload log: "+this.src+" tried too more than "+this.options.attempt+" times"),void e()):this.state.rendered&&this.state.loaded?void 0:this._imageCache.has(this.src)?(this.state.loaded=!0,this.render("loaded",!0),this.state.rendered=!0,e()):void this.renderLoading((function(){t.attempt++,t.options.adapter.beforeLoad&&t.options.adapter.beforeLoad(t,t.options),t.record("loadStart"),L({src:t.src,cors:t.cors},(function(n){t.naturalHeight=n.naturalHeight,t.naturalWidth=n.naturalWidth,t.state.loaded=!0,t.state.error=!1,t.record("loadEnd"),t.render("loaded",!1),t.state.rendered=!0,t._imageCache.add(t.src),e()}),(function(e){!t.options.silent&&console.error(e),t.state.error=!0,t.state.loaded=!1,t.render("error",!1)}))}))}},{key:"render",value:function(t,e){this.elRenderer(this,t,e)}},{key:"performance",value:function(){var t="loading",e=0;return this.state.loaded&&(t="loaded",e=(this.performanceData.loadEnd-this.performanceData.loadStart)/1e3),this.state.error&&(t="error"),{src:this.src,state:t,time:e}}},{key:"$destroy",value:function(){this.el=null,this.src=null,this.error=null,this.loading=null,this.bindType=null,this.attempt=0}}]),t}(),A="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",T=["scroll","wheel","mousewheel","resize","animationend","transitionend","touchmove"],O={rootMargin:"0px",threshold:0};function $(t){return function(){function e(t){var n=t.preLoad,i=t.error,r=t.throttleWait,o=t.preLoadTop,a=t.dispatchEvent,u=t.loading,l=t.attempt,d=t.silent,f=void 0===d||d,v=t.scale,p=t.listenEvents;t.hasbind;var y,m,w,L,_,z,E=t.filter,x=t.adapter,$=t.observer,I=t.observerOptions;s(this,e),this.version='"1.3.5"',this.mode=c,this.ListenerQueue=[],this.TargetIndex=0,this.TargetQueue=[],this.options={silent:f,dispatchEvent:!!a,throttleWait:r||200,preLoad:n||1.3,preLoadTop:o||0,error:i||A,loading:u||A,attempt:l||3,scale:v||g(v),ListenEvents:p||T,hasbind:!1,supportWebp:b(),filter:E||{},adapter:x||{},observer:!!$,observerOptions:I||O},this._initEvent(),this._imageCache=new k({max:200}),this.lazyLoadHandler=(y=this._lazyLoadHandler.bind(this),m=this.options.throttleWait,w=null,L=null,_=0,z=!1,function(){if(z=!0,!w){var t=Date.now()-_,e=this,n=arguments,i=function(){_=Date.now(),w=!1,y.apply(e,n)};t>=m?i():w=setTimeout(i,m),z&&(clearTimeout(L),L=setTimeout(i,2*m))}}),this.setMode(this.options.observer?h:c)}return a(e,[{key:"config",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};u(this.options,t)}},{key:"performance",value:function(){var t=[];return this.ListenerQueue.map((function(e){t.push(e.performance())})),t}},{key:"addLazyBox",value:function(t){this.ListenerQueue.push(t),l&&(this._addListenerTarget(window),this._observer&&this._observer.observe(t.el),t.$el&&t.$el.parentNode&&this._addListenerTarget(t.$el.parentNode))}},{key:"add",value:function(e,n,i){var r=this;if(function(t,e){for(var n=!1,i=0,r=t.length;i1&&void 0!==arguments[1]?arguments[1]:{},n=new($(t))(e);"2"===t.version.split(".")[0]?t.directive("lazy",{bind:n.add.bind(n),update:n.update.bind(n),componentUpdated:n.lazyLoadHandler.bind(n),unbind:n.remove.bind(n)}):t.directive("lazy",{bind:n.lazyLoadHandler.bind(n),update:function(t,e){u(this.vm.$refs,this.vm.$els),n.add(this.el,{modifiers:this.modifiers||{},arg:this.arg,value:t,oldValue:e},{context:this.vm})},unbind:function(){n.remove(this.el)}})};var I=function(t){return{props:{tag:{type:String,default:"div"}},render:function(t){return t(this.tag,null,this.show?this.$slots.default:null)},data:function(){return{el:null,state:{loaded:!1},rect:{},show:!1}},mounted:function(){this.el=this.$el,t.addLazyBox(this),t.lazyLoadHandler()},beforeDestroy:function(){t.removeComponent(this)},methods:{getRect:function(){this.rect=this.$el.getBoundingClientRect()},checkInView:function(){return this.getRect(),l&&this.rect.top0&&this.rect.left0},load:function(){this.show=!0,this.state.loaded=!0,this.$emit("show",this)},destroy:function(){return this.$destroy}}}};I.install=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=new($(t))(e);t.component("lazy-component",I(n))};var C=function(){function t(e){var n=e.lazy;s(this,t),this.lazy=n,n.lazyContainerMananger=this,this._queue=[]}return a(t,[{key:"bind",value:function(t,e,n){var i=new H({el:t,binding:e,vnode:n,lazy:this.lazy});this._queue.push(i)}},{key:"update",value:function(t,e,n){var i=y(this._queue,(function(e){return e.el===t}));i&&i.update({el:t,binding:e,vnode:n})}},{key:"unbind",value:function(t,e,n){var i=y(this._queue,(function(e){return e.el===t}));i&&(i.clear(),v(this._queue,i))}}]),t}(),S={selector:"img"},H=function(){function t(e){var n=e.el,i=e.binding,r=e.vnode,o=e.lazy;s(this,t),this.el=null,this.vnode=r,this.binding=i,this.options={},this.lazy=o,this._queue=[],this.update({el:n,binding:i})}return a(t,[{key:"update",value:function(t){var e=this,n=t.el,i=t.binding;this.el=n,this.options=u({},S,i.value),this.getImgs().forEach((function(t){e.lazy.add(t,u({},e.binding,{value:{src:"dataset"in t?t.dataset.src:t.getAttribute("data-src"),error:("dataset"in t?t.dataset.error:t.getAttribute("data-error"))||e.options.error,loading:("dataset"in t?t.dataset.loading:t.getAttribute("data-loading"))||e.options.loading}}),e.vnode)}))}},{key:"getImgs",value:function(){return function(t){for(var e=t.length,n=[],i=0;i1&&void 0!==arguments[1]?arguments[1]:{},n=new($(t))(e),i=new H({lazy:n});"2"===t.version.split(".")[0]?t.directive("lazy-container",{bind:i.bind.bind(i),componentUpdated:i.update.bind(i),unbind:i.unbind.bind(i)}):t.directive("lazy-container",{update:function(t,e){i.update(this.el,{modifiers:this.modifiers||{},arg:this.arg,value:t,oldValue:e},{context:this.vm})},unbind:function(){i.unbind(this.el)}})};var j=function(t){return{props:{src:[String,Object],tag:{type:String,default:"img"}},render:function(t){return t(this.tag,{attrs:{src:this.renderSrc}},this.$slots.default)},data:function(){return{el:null,options:{src:"",error:"",loading:"",attempt:t.options.attempt},state:{loaded:!1,error:!1,attempt:0},rect:{},renderSrc:""}},watch:{src:function(){this.init(),t.addLazyBox(this),t.lazyLoadHandler()}},created:function(){this.init(),this.renderSrc=this.options.loading},mounted:function(){this.el=this.$el,t.addLazyBox(this),t.lazyLoadHandler()},beforeDestroy:function(){t.removeComponent(this)},methods:{init:function(){var e=t._valueFormatter(this.src),n=e.src,i=e.loading,r=e.error;this.state.loaded=!1,this.options.src=n,this.options.error=r,this.options.loading=i,this.renderSrc=this.options.loading},getRect:function(){this.rect=this.$el.getBoundingClientRect()},checkInView:function(){return this.getRect(),l&&this.rect.top0&&this.rect.left0},load:function(){var e=this,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:E;if(this.state.attempt>this.options.attempt-1&&this.state.error)return t.options.silent||console.log("VueLazyload log: "+this.options.src+" tried too more than "+this.options.attempt+" times"),void n();var i=this.options.src;L({src:i},(function(t){var n=t.src;e.renderSrc=n,e.state.loaded=!0}),(function(t){e.state.attempt++,e.renderSrc=e.options.error,e.state.error=!0}))}}}};j.install=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=new($(t))(e);t.component("lazy-image",j(n))};var Q={install:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=new($(t))(e),i=new C({lazy:n}),r="2"===t.version.split(".")[0];t.prototype.$Lazyload=n,e.lazyComponent&&t.component("lazy-component",I(n)),e.lazyImage&&t.component("lazy-image",j(n)),r?(t.directive("lazy",{bind:n.add.bind(n),update:n.update.bind(n),componentUpdated:n.lazyLoadHandler.bind(n),unbind:n.remove.bind(n)}),t.directive("lazy-container",{bind:i.bind.bind(i),componentUpdated:i.update.bind(i),unbind:i.unbind.bind(i)})):(t.directive("lazy",{bind:n.lazyLoadHandler.bind(n),update:function(t,e){u(this.vm.$refs,this.vm.$els),n.add(this.el,{modifiers:this.modifiers||{},arg:this.arg,value:t,oldValue:e},{context:this.vm})},unbind:function(){n.remove(this.el)}}),t.directive("lazy-container",{update:function(t,e){i.update(this.el,{modifiers:this.modifiers||{},arg:this.arg,value:t,oldValue:e},{context:this.vm})},unbind:function(){i.unbind(this.el)}}))}};t.Lazy=$,t.LazyComponent=I,t.LazyContainer=C,t.LazyImage=j,t.default=Q,Object.defineProperty(t,"__esModule",{value:!0})}));