Full Code of hilongjw/vue-lazyload for AI

master 0d9a95ee9a09 cached
26 files
123.6 KB
33.7k tokens
118 symbols
1 requests
Download .txt
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

[![Build Status](https://img.shields.io/circleci/project/hilongjw/vue-lazyload/master.svg?style=flat-square)](https://circleci.com/gh/hilongjw/vue-lazyload)
[![npm version](https://img.shields.io/npm/v/vue-lazyload.svg?style=flat-square)](http://badge.fury.io/js/vue-lazyload)
[![npm downloads](https://img.shields.io/npm/dm/vue-lazyload.svg?style=flat-square)](http://badge.fury.io/js/vue-lazyload)
[![npm license](https://img.shields.io/npm/l/vue-lazyload.svg?style=flat-square)](http://badge.fury.io/js/vue-lazyload)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
[![CDNJS version](https://img.shields.io/cdnjs/v/vue-lazyload.svg)](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
<script src="https://unpkg.com/vue-lazyload/vue-lazyload.js"></script>
<script>
  Vue.use(VueLazyload)
  ...
</script>

```

# 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
<ul>
  <li v-for="img in list">
    <img v-lazy="img.src" >
  </li>
</ul>
```

use `v-lazy-container` work with raw HTML

```html
<div v-lazy-container="{ selector: 'img' }">
  <img data-src="//domain.com/img1.jpg">
  <img data-src="//domain.com/img2.jpg">
  <img data-src="//domain.com/img3.jpg">  
</div>
```

custom `error` and `loading` placeholder image

```html
<div v-lazy-container="{ selector: 'img', error: 'xxx.jpg', loading: 'xxx.jpg' }">
  <img data-src="//domain.com/img1.jpg">
  <img data-src="//domain.com/img2.jpg">
  <img data-src="//domain.com/img3.jpg">  
</div>
```

```html
<div v-lazy-container="{ selector: 'img' }">
  <img data-src="//domain.com/img1.jpg" data-error="xxx.jpg">
  <img data-src="//domain.com/img2.jpg" data-loading="xxx.jpg">
  <img data-src="//domain.com/img3.jpg">  
</div>
```

## 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
<lazy-component @show="handler">
  <img class="mini-cover" :src="img.src" width="100%" height="400">
</lazy-component>

<script>
  {
    ...
    methods: {
      handler (component) {
        console.log('this component is showing')
      }
    }

  }
</script>
```
Use in list
```html
<lazy-component v-for="(item, index) in list" :key="item.src" >
  <img class="mini-cover" :src="item.src" width="100%" height="400">
</lazy-component>
```


## Implementation

### Basic

vue-lazyload will set this img element's `src` with `imgUrl` string

```html
<script>
export default {
  data () {
    return {
      imgObj: {
        src: 'http://xx.com/logo.png',
        error: 'http://xx.com/error.png',
        loading: 'http://xx.com/loading-spin.svg'
      },
      imgUrl: 'http://xx.com/logo.png' // String
    }
  }
}
</script>

<template>
  <div ref="container">
     <img v-lazy="imgUrl"/>
     <div v-lazy:background-image="imgUrl"></div>

     <!-- with customer error and loading -->
     <img v-lazy="imgObj"/>
     <div v-lazy:background-image="imgObj"></div>

     <!-- Customer scrollable element -->
     <img v-lazy.container ="imgUrl"/>
     <div v-lazy:background-image.container="img"></div>

    <!-- srcset -->
    <img v-lazy="'img.400px.jpg'" data-srcset="img.400px.jpg 400w, img.800px.jpg 800w, img.1200px.jpg 1200w">
    <img v-lazy="imgUrl" :data-srcset="imgUrl' + '?size=400 400w, ' + imgUrl + ' ?size=800 800w, ' + imgUrl +'/1200.jpg 1200w'" />
  </div>
</template>
```

### CSS state

There are three states while img loading

`loading`  `loaded`  `error`

```html
<img src="imgUrl" lazy="loading">
<img src="imgUrl" lazy="loaded">
<img src="imgUrl" lazy="error">
```

```html
<style>
  img[lazy=loading] {
    /*your style here*/
  }
  img[lazy=error] {
    /*your style here*/
  }
  img[lazy=loaded] {
    /*your style here*/
  }
  /*
  or background-image
  */
  .yourclass[lazy=loading] {
    /*your style here*/
  }
  .yourclass[lazy=error] {
    /*your style here*/
  }
  .yourclass[lazy=loaded] {
    /*your style here*/
  }
</style>
```

## 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())
})
```

![performance-demo](http://ww1.sinaimg.cn/large/69402bf8gw1fbo62ocvlaj213k09w78w.jpg)

### Dynamic switching pictures

```vue
 <img v-lazy="lazyImg" :key="lazyImg.src">
```


# 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 <hilongjw@gmail.com>\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 <hilongjw@gmail.com>",
  "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<VueLazyloadOptions> {}


================================================
FILE: types/test/index.ts
================================================
import Vue from "vue";
import VueLazyload, { VueLazyloadOptions } from "../index";

Vue.use<VueLazyloadOptions>(VueLazyload);

Vue.use<VueLazyloadOptions>(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 <hilongjw@gmail.com>
 * 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 <hilongjw@gmail.com>
 * 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;s<r;s++)o[s-1]=arguments[s];if("function"!=typeof(a=t)&&"[object Object]"!==e.call(a)&&!Array.isArray(a))throw new TypeError("expected the first argument to be an object");var a;if(0===o.length||"function"!=typeof Symbol||"function"!=typeof i)return t;var u=!0,l=!1,d=void 0;try{for(var c,h=o[Symbol.iterator]();!(u=(c=h.next()).done);u=!0){var f=c.value,v=i(f),p=!0,y=!1,g=void 0;try{for(var b,m=v[Symbol.iterator]();!(p=(b=m.next()).done);p=!0){var w=b.value;n.call(f,w)&&(t[w]=f[w])}}catch(t){y=!0,g=t}finally{try{!p&&m.return&&m.return()}finally{if(y)throw g}}}}catch(t){l=!0,d=t}finally{try{!u&&h.return&&h.return()}finally{if(l)throw d}}return t}})),i=Object.freeze({__proto__:null,default:n,__moduleExports:n}),r=i&&n||i,o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},s=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},a=function(){function t(t,e){for(var n=0;n<e.length;n++){var i=e[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,i.key,i)}}return function(e,n,i){return n&&t(e.prototype,n),i&&t(e,i),e}}(),u=e((function(t){var e=Object.prototype.toString,n=function(t){return"__proto__"!==t&&"constructor"!==t&&"prototype"!==t},i=t.exports=function(t){for(var e=arguments.length,a=Array(e>1?e-1:0),u=1;u<e;u++)a[u-1]=arguments[u];var l,d=0;for(("object"===(void 0===(l=t)?"undefined":o(l))?null===l:"function"!=typeof l)&&(t=a[d++]),t||(t={});d<a.length;d++)if(s(a[d])){var c=!0,h=!1,f=void 0;try{for(var v,p=Object.keys(a[d])[Symbol.iterator]();!(c=(v=p.next()).done);c=!0){var y=v.value;n(y)&&(s(t[y])&&s(a[d][y])?i(t[y],a[d][y]):t[y]=a[d][y])}}catch(t){h=!0,f=t}finally{try{!c&&p.return&&p.return()}finally{if(h)throw f}}r(t,a[d])}return t};function s(t){return"function"==typeof t||"[object Object]"===e.call(t)}})),l="undefined"!=typeof window&&null!==window,d=function(){if(l&&"IntersectionObserver"in window&&"IntersectionObserverEntry"in window&&"intersectionRatio"in window.IntersectionObserverEntry.prototype)return"isIntersecting"in window.IntersectionObserverEntry.prototype||Object.defineProperty(window.IntersectionObserverEntry.prototype,"isIntersecting",{get:function(){return this.intersectionRatio>0}}),!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])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;d<i.length;d++){u=(l=i[d])[1];var c=i[d+1];if(c&&c[0]<r){u=l[1];break}if(!c){u=l[1];break}}return u}}function y(t,e){for(var n=void 0,i=0,r=t.length;i<r;i++)if(e(t[i])){n=t[i];break}return n}var g=function(){var t=arguments.length>0&&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.top<window.innerHeight*this.options.preLoad&&this.rect.bottom>this.options.preLoadTop&&this.rect.left<window.innerWidth*this.options.preLoad&&this.rect.right>0}},{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;i<r;i++)if(e(t[i])){n=!0;break}return n}(this.ListenerQueue,(function(t){return t.el===e})))return this.update(e,n),t.nextTick(this.lazyLoadHandler);var o=this._valueFormatter(n.value),s=o.src,a=o.loading,u=o.error,d=o.cors;t.nextTick((function(){s=p(e,r.options.scale)||s,r._observer&&r._observer.observe(e);var o=Object.keys(n.modifiers)[0],c=void 0;o&&(c=(c=i.context.$refs[o])?c.$el||c:document.getElementById(o)),c||(c=function(t){if(l){if(!(t instanceof HTMLElement))return window;for(var e=t;e&&e!==document.body&&e!==document.documentElement&&e.parentNode;){if(/(scroll|auto)/.test(z(e)))return e;e=e.parentNode}return window}}(e));var h=new x({bindType:n.arg,$parent:c,el:e,loading:a,error:u,src:s,cors:d,elRenderer:r._elRenderer.bind(r),options:r.options,imageCache:r._imageCache});r.ListenerQueue.push(h),l&&(r._addListenerTarget(window),r._addListenerTarget(c)),r.lazyLoadHandler(),t.nextTick((function(){return r.lazyLoadHandler()}))}))}},{key:"update",value:function(e,n,i){var r=this,o=this._valueFormatter(n.value),s=o.src,a=o.loading,u=o.error;s=p(e,this.options.scale)||s;var l=y(this.ListenerQueue,(function(t){return t.el===e}));l?l.update({src:s,loading:a,error:u}):this.add(e,n,i),this._observer&&(this._observer.unobserve(e),this._observer.observe(e)),this.lazyLoadHandler(),t.nextTick((function(){return r.lazyLoadHandler()}))}},{key:"remove",value:function(t){if(t){this._observer&&this._observer.unobserve(t);var e=y(this.ListenerQueue,(function(e){return e.el===t}));e&&(this._removeListenerTarget(e.$parent),this._removeListenerTarget(window),v(this.ListenerQueue,e),e.$destroy())}}},{key:"removeComponent",value:function(t){t&&(v(this.ListenerQueue,t),this._observer&&this._observer.unobserve(t.el),t.$parent&&t.$el.parentNode&&this._removeListenerTarget(t.$el.parentNode),this._removeListenerTarget(window))}},{key:"setMode",value:function(t){var e=this;d||t!==h||(t=c),this.mode=t,t===c?(this._observer&&(this.ListenerQueue.forEach((function(t){e._observer.unobserve(t.el)})),this._observer=null),this.TargetQueue.forEach((function(t){e._initListen(t.el,!0)}))):(this.TargetQueue.forEach((function(t){e._initListen(t.el,!1)})),this._initIntersectionObserver())}},{key:"_addListenerTarget",value:function(t){if(t){var e=y(this.TargetQueue,(function(e){return e.el===t}));return e?e.childrenCount++:(e={el:t,id:++this.TargetIndex,childrenCount:1,listened:!0},this.mode===c&&this._initListen(e.el,!0),this.TargetQueue.push(e)),this.TargetIndex}}},{key:"_removeListenerTarget",value:function(t){var e=this;this.TargetQueue.forEach((function(n,i){n.el===t&&(n.childrenCount--,n.childrenCount||(e._initListen(n.el,!1),e.TargetQueue.splice(i,1),n=null))}))}},{key:"_initListen",value:function(t,e){var n=this;this.options.ListenEvents.forEach((function(i){return w[e?"on":"off"](t,i,n.lazyLoadHandler)}))}},{key:"_initEvent",value:function(){var t=this;this.Event={listeners:{loading:[],loaded:[],error:[]}},this.$on=function(e,n){t.Event.listeners[e]||(t.Event.listeners[e]=[]),t.Event.listeners[e].push(n)},this.$once=function(e,n){var i=t;t.$on(e,(function t(){i.$off(e,t),n.apply(i,arguments)}))},this.$off=function(e,n){if(n)v(t.Event.listeners[e],n);else{if(!t.Event.listeners[e])return;t.Event.listeners[e].length=0}},this.$emit=function(e,n,i){t.Event.listeners[e]&&t.Event.listeners[e].forEach((function(t){return t(n,i)}))}}},{key:"_lazyLoadHandler",value:function(){var t=this,e=[];this.ListenerQueue.forEach((function(t,n){t.el&&t.el.parentNode||e.push(t),t.checkInView()&&t.load()})),e.forEach((function(e){v(t.ListenerQueue,e),e.$destroy()}))}},{key:"_initIntersectionObserver",value:function(){var t=this;d&&(this._observer=new IntersectionObserver(this._observerHandler.bind(this),this.options.observerOptions),this.ListenerQueue.length&&this.ListenerQueue.forEach((function(e){t._observer.observe(e.el)})))}},{key:"_observerHandler",value:function(t,e){var n=this;t.forEach((function(t){t.isIntersecting&&n.ListenerQueue.forEach((function(e){if(e.el===t.target){if(e.state.loaded)return n._observer.unobserve(e.el);e.load()}}))}))}},{key:"_elRenderer",value:function(t,e,n){if(t.el){var i=t.el,r=t.bindType,o=void 0;switch(e){case"loading":o=t.loading;break;case"error":o=t.error;break;default:o=t.src}if(r?i.style[r]='url("'+o+'")':i.getAttribute("src")!==o&&i.setAttribute("src",o),i.setAttribute("lazy",e),this.$emit(e,t,n),this.options.adapter[e]&&this.options.adapter[e](t,this.options),this.options.dispatchEvent){var s=new f(e,{detail:t});i.dispatchEvent(s)}}}},{key:"_valueFormatter",value:function(t){var e,n=t,i=this.options.loading,r=this.options.error;return null!==(e=t)&&"object"===(void 0===e?"undefined":o(e))&&(t.src||this.options.silent||console.error("Vue Lazyload warning: miss src with "+t),n=t.src,i=t.loading||this.options.loading,r=t.error||this.options.error),{src:n,loading:i,error:r}}}]),e}()}$.install=function(t){var e=arguments.length>1&&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.top<window.innerHeight*t.options.preLoad&&this.rect.bottom>0&&this.rect.left<window.innerWidth*t.options.preLoad&&this.rect.right>0},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;i<e;i++)n.push(t[i]);return n}(this.el.querySelectorAll(this.options.selector))}},{key:"clear",value:function(){var t=this;this.getImgs().forEach((function(e){return t.lazy.remove(e)})),this.vnode=null,this.binding=null,this.lazy=null}}]),t}();H.install=function(t){var e=arguments.length>1&&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.top<window.innerHeight*t.options.preLoad&&this.rect.bottom>0&&this.rect.left<window.innerWidth*t.options.preLoad&&this.rect.right>0},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})}));
Download .txt
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
Download .txt
SYMBOL INDEX (118 symbols across 12 files)

FILE: build.js
  function build (line 17) | async function build (options, _outputOptions) {
  function getSize (line 36) | function getSize (code) {
  function blue (line 40) | function blue (str) {

FILE: src/index.js
  method install (line 13) | install (Vue, options = {}) {

FILE: src/lazy-component.js
  method render (line 12) | render (h) {
  method data (line 15) | data () {
  method mounted (line 25) | mounted () {
  method beforeDestroy (line 30) | beforeDestroy () {
  method getRect (line 34) | getRect () {
  method checkInView (line 37) | checkInView () {
  method load (line 43) | load () {
  method destroy (line 48) | destroy () {

FILE: src/lazy-container.js
  class LazyContainerMananger (line 9) | class LazyContainerMananger {
    method constructor (line 10) | constructor ({ lazy }) {
    method bind (line 16) | bind (el, binding, vnode) {
    method update (line 21) | update (el, binding, vnode) {
    method unbind (line 27) | unbind (el, binding, vnode) {
  class LazyContainer (line 39) | class LazyContainer {
    method constructor (line 40) | constructor ({ el, binding, vnode, lazy }) {
    method update (line 51) | update ({ el, binding }) {
    method getImgs (line 67) | getImgs () {
    method clear (line 71) | clear () {
  method update (line 95) | update (newValue, oldValue) {
  method unbind (line 105) | unbind () {

FILE: src/lazy-image.js
  method render (line 17) | render (h) {
  method data (line 24) | data () {
  method src (line 43) | src () {
  method created (line 49) | created () {
  method mounted (line 53) | mounted () {
  method beforeDestroy (line 58) | beforeDestroy () {
  method init (line 62) | init () {
  method getRect (line 70) | getRect () {
  method checkInView (line 73) | checkInView () {
  method load (line 79) | load (onFinish = noop) {

FILE: src/lazy.js
  constant DEFAULT_URL (line 22) | const DEFAULT_URL = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5B...
  constant DEFAULT_EVENTS (line 23) | const DEFAULT_EVENTS = ['scroll', 'wheel', 'mousewheel', 'resize', 'anim...
  constant DEFAULT_OBSERVER_OPTIONS (line 24) | const DEFAULT_OBSERVER_OPTIONS = {
  function Lazy (line 29) | function Lazy (Vue) {
  method update (line 472) | update (newValue, oldValue) {
  method unbind (line 483) | unbind () {

FILE: src/listener.js
  class ReactiveListener (line 14) | class ReactiveListener {
    method constructor (line 15) | constructor ({ el, src, error, loading, bindType, $parent, options, co...
    method initState (line 49) | initState () {
    method record (line 68) | record (event) {
    method update (line 79) | update ({ src, loading, error }) {
    method getRect (line 95) | getRect () {
    method checkInView (line 103) | checkInView () {
    method filter (line 112) | filter () {
    method renderLoading (line 123) | renderLoading (cb) {
    method load (line 144) | load (onFinish = noop) {
    method render (line 192) | render (state, cache) {
    method performance (line 200) | performance () {
    method $destroy (line 222) | $destroy () {

FILE: src/util.js
  function checkIntersectionObserver (line 7) | function checkIntersectionObserver () {
  function CustomEvent (line 37) | function CustomEvent (event, params) {
  function remove (line 47) | function remove (arr, item) {
  function some (line 53) | function some (arr, fn) {
  function getBestSelectionFromSrcset (line 64) | function getBestSelectionFromSrcset (el, scale) {
  function find (line 127) | function find (arr, fn) {
  function supportWebp (line 140) | function supportWebp () {
  function throttle (line 158) | function throttle (action, delay) {
  function testSupportsPassive (line 188) | function testSupportsPassive () {
  method on (line 205) | on (el, type, func, capture = false) {
  method off (line 215) | off (el, type, func, capture = false) {
  function isObject (line 282) | function isObject (obj) {
  function ObjectKeys (line 286) | function ObjectKeys (obj) {
  function ArrayFrom (line 301) | function ArrayFrom (arrLike) {
  function noop (line 310) | function noop () {}
  class ImageCache (line 312) | class ImageCache {
    method constructor (line 313) | constructor ({ max }) {
    method has (line 320) | has (key) {
    method add (line 324) | add (key) {
    method free (line 332) | free () {

FILE: types/lazyload.d.ts
  type IntersectionObserverInit (line 3) | interface IntersectionObserverInit {
  type VueLazyloadImage (line 9) | interface VueLazyloadImage {
  type VueLazyloadOptions (line 15) | interface VueLazyloadOptions {
  type VueReactiveListener (line 34) | interface VueReactiveListener {
  type VueLazyloadListenEvent (line 54) | interface VueLazyloadListenEvent {
  type VueLazyloadHandler (line 58) | interface VueLazyloadHandler {
  type VueLazyloadPluginObject (line 65) | interface VueLazyloadPluginObject extends PluginObject<VueLazyloadOption...

FILE: types/vue.d.ts
  type Vue (line 9) | interface Vue {

FILE: vue-lazyload.esm.js
  function createCommonjsModule (line 7) | function createCommonjsModule(fn, module) {
  function isObject (line 85) | function isObject(val) {
  function defineProperties (line 111) | function defineProperties(target, props) {
  function isObject (line 183) | function isObject(val) {
  function isPrimitive (line 187) | function isPrimitive(val) {
  function checkIntersectionObserver (line 196) | function checkIntersectionObserver() {
  function CustomEvent (line 221) | function CustomEvent(event, params) {
  function remove (line 231) | function remove(arr, item) {
  function some (line 237) | function some(arr, fn) {
  function getBestSelectionFromSrcset (line 248) | function getBestSelectionFromSrcset(el, scale) {
  function find (line 311) | function find(arr, fn) {
  function supportWebp (line 327) | function supportWebp() {
  function throttle (line 345) | function throttle(action, delay) {
  function testSupportsPassive (line 375) | function testSupportsPassive() {
  function isObject (line 471) | function isObject(obj) {
  function ObjectKeys (line 475) | function ObjectKeys(obj) {
  function ArrayFrom (line 490) | function ArrayFrom(arrLike) {
  function noop (line 499) | function noop() {}
  function ImageCache (line 502) | function ImageCache(_ref) {
  function ReactiveListener (line 543) | function ReactiveListener(_ref) {
  function Lazy (line 826) | function Lazy(Vue) {
  function LazyContainerMananger (line 1462) | function LazyContainerMananger(_ref) {
  function LazyContainer (line 1506) | function LazyContainer(_ref2) {

FILE: vue-lazyload.js
  function e (line 6) | function e(t,e){return t(e={exports:{}},e.exports),e.exports}
  function t (line 6) | function t(t,e){for(var n=0;n<e.length;n++){var i=e[n];i.enumerable=i.en...
  function s (line 6) | function s(t){return"function"==typeof t||"[object Object]"===e.call(t)}
  function t (line 6) | function t(t,e){e=e||{bubbles:!1,cancelable:!1,detail:void 0};var n=docu...
  function v (line 6) | function v(t,e){if(t.length){var n=t.indexOf(e);return n>-1?t.splice(n,1...
  function p (line 6) | function p(t,e){if("IMG"===t.tagName&&t.getAttribute("data-srcset")){var...
  function y (line 6) | function y(t,e){for(var n=void 0,i=0,r=t.length;i<r;i++)if(e(t[i])){n=t[...
  function b (line 6) | function b(){if(!l)return!1;var t=!0;try{var e=document.createElement("c...
  function E (line 6) | function E(){}
  function t (line 6) | function t(e){var n=e.max;s(this,t),this.options={max:n||100},this._cach...
  function t (line 6) | function t(e){var n=e.el,i=e.src,r=e.error,o=e.loading,a=e.bindType,u=e....
  function $ (line 6) | function $(t){return function(){function e(t){var n=t.preLoad,i=t.error,...
  function t (line 6) | function t(e){var n=e.lazy;s(this,t),this.lazy=n,n.lazyContainerMananger...
  function t (line 6) | function t(e){var n=e.el,i=e.binding,r=e.vnode,o=e.lazy;s(this,t),this.e...
Condensed preview — 26 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (133K chars).
[
  {
    "path": ".babelrc",
    "chars": 212,
    "preview": "{\n    \"presets\": [\n        [\"env\", {\n            \"modules\": false\n        }]\n    ],\n    \"plugins\": [\n        \"syntax-dyn"
  },
  {
    "path": ".circleci/config.yml",
    "chars": 1128,
    "preview": "# Javascript Node CircleCI 2.0 configuration file\n#\n# Check https://circleci.com/docs/2.0/language-javascript/ for more "
  },
  {
    "path": ".circleci/setup_puppeteer.sh",
    "chars": 511,
    "preview": "#!/bin/bash\n\nsudo apt-get update\nsudo apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 "
  },
  {
    "path": ".editorconfig",
    "chars": 161,
    "preview": "root = true\n\n[*]\nindent_style = space\nindent_size = 4\ntab_width = 4\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_white"
  },
  {
    "path": ".eslintrc",
    "chars": 117,
    "preview": "{\n  \"extends\": \"standard\",\n  \"env\": {\n    \"browser\": true,\n    \"es6\": true,\n    \"jest\": true,\n    \"mocha\": true\n  }\n}"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "chars": 2353,
    "preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
  },
  {
    "path": ".gitignore",
    "chars": 70,
    "preview": ".DS_Store\nnode_modules\ndist\nlib\nnpm-debug.log\npackage-lock.json\n.idea\n"
  },
  {
    "path": "LICENSE",
    "chars": 1070,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Awe\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
  },
  {
    "path": "README.md",
    "chars": 11047,
    "preview": "# Vue-Lazyload\n\n[![Build Status](https://img.shields.io/circleci/project/hilongjw/vue-lazyload/master.svg?style=flat-squ"
  },
  {
    "path": "build.js",
    "chars": 1892,
    "preview": "const path = require('path')\nconst rollup = require('rollup')\nconst babel = require('rollup-plugin-babel')\nconst replace"
  },
  {
    "path": "package.json",
    "chars": 1848,
    "preview": "{\n  \"name\": \"vue-lazyload\",\n  \"version\": \"1.3.5\",\n  \"description\": \"Vue module for lazy-loading images in your vue.js ap"
  },
  {
    "path": "src/index.js",
    "chars": 2148,
    "preview": "import Lazy from './lazy'\nimport LazyComponent from './lazy-component'\nimport LazyContainer from './lazy-container'\nimpo"
  },
  {
    "path": "src/lazy-component.js",
    "chars": 1393,
    "preview": "import { inBrowser } from './util'\nimport Lazy from './lazy'\n\nconst LazyComponent = (lazy) => {\n  return {\n    props: {\n"
  },
  {
    "path": "src/lazy-container.js",
    "chars": 2721,
    "preview": "import {\n  find,\n  remove,\n  assign,\n  ArrayFrom\n} from './util'\nimport Lazy from './lazy'\n\nexport default class LazyCon"
  },
  {
    "path": "src/lazy-image.js",
    "chars": 2642,
    "preview": "import {\n  inBrowser,\n  loadImageAsync,\n  noop\n} from './util'\nimport Lazy from './lazy'\n\nconst LazyImage = (lazyManager"
  },
  {
    "path": "src/lazy.js",
    "chars": 12884,
    "preview": "import {\n  inBrowser,\n  CustomEvent,\n  remove,\n  some,\n  find,\n  _,\n  throttle,\n  supportWebp,\n  getDPR,\n  scrollParent,"
  },
  {
    "path": "src/listener.js",
    "chars": 4919,
    "preview": "import {\n  loadImageAsync,\n  ObjectKeys,\n  noop\n} from './util'\n\n// el: {\n//     state,\n//     src,\n//     error,\n//    "
  },
  {
    "path": "src/util.js",
    "chars": 7418,
    "preview": "import assign from 'assign-deep'\n\nconst inBrowser = typeof window !== 'undefined' && window !== null\n\nexport const hasIn"
  },
  {
    "path": "test/test.spec.js",
    "chars": 1066,
    "preview": "import Vue from 'vue'\nimport VueLazyload from '../src'\nimport genLazyCore from '../src/lazy'\nimport assert from 'assert'"
  },
  {
    "path": "types/index.d.ts",
    "chars": 266,
    "preview": "import \"./vue\";\nimport { VueLazyloadPluginObject } from \"./lazyload\";\n\ndeclare var VueLazyload: VueLazyloadPluginObject;"
  },
  {
    "path": "types/lazyload.d.ts",
    "chars": 1482,
    "preview": "import { PluginObject } from \"vue\";\n\ninterface IntersectionObserverInit {\n  root?: Element | null;\n  rootMargin?: string"
  },
  {
    "path": "types/test/index.ts",
    "chars": 400,
    "preview": "import Vue from \"vue\";\nimport VueLazyload, { VueLazyloadOptions } from \"../index\";\n\nVue.use<VueLazyloadOptions>(VueLazyl"
  },
  {
    "path": "types/test/tsconfig.json",
    "chars": 284,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"module\": \"es2015\",\n    \"moduleResolution\": \"node\",\n    \"lib\": [\n     "
  },
  {
    "path": "types/vue.d.ts",
    "chars": 205,
    "preview": "/**\n * Augment the typings of Vue.js\n */\n\nimport Vue from \"vue\";\nimport { VueLazyloadHandler } from \"./index\";\n\ndeclare "
  },
  {
    "path": "vue-lazyload.esm.js",
    "chars": 46812,
    "preview": "/*!\n * Vue-Lazyload.js v1.3.5\n * (c) 2023 Awe <hilongjw@gmail.com>\n * Released under the MIT License.\n */\n\nfunction crea"
  },
  {
    "path": "vue-lazyload.js",
    "chars": 21521,
    "preview": "/*!\n * Vue-Lazyload.js v1.3.5\n * (c) 2023 Awe <hilongjw@gmail.com>\n * Released under the MIT License.\n */\n!function(t,e)"
  }
]

About this extraction

This page contains the full source code of the hilongjw/vue-lazyload GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 26 files (123.6 KB), approximately 33.7k tokens, and a symbol index with 118 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!