Repository: verlok/vanilla-lazyload Branch: master Commit: 5123ea8e1ed1 Files: 216 Total size: 868.7 KB Directory structure: gitextract_cukr4pm0/ ├── .babelrc ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github/ │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── feature_request.md │ │ └── support-request.md │ └── workflows/ │ ├── ci.yml │ └── playwright.yml ├── .gitignore ├── .prettierrc ├── .vscode/ │ └── settings.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── UPGRADE.md ├── _config.yml ├── bower.json ├── currentFeature.md ├── demos/ │ ├── async.html │ ├── async_multiple.html │ ├── background_images.html │ ├── background_images_image-set.html │ ├── background_images_multi.html │ ├── container_multiple.html │ ├── container_single.html │ ├── dynamic_content.html │ ├── dynamic_content_nodeset.html │ ├── embedded.html │ ├── error_no_restore.html │ ├── error_restore.html │ ├── esm.html │ ├── fade_in.html │ ├── hundreds.html │ ├── iframes/ │ │ ├── i01.html │ │ ├── i02.html │ │ └── i03.html │ ├── iframes.html │ ├── image_basic.html │ ├── image_hidden.html │ ├── image_no_classes.html │ ├── image_ph_external.html │ ├── image_ph_inline.html │ ├── image_srcset.html │ ├── image_srcset_lazy_sizes.html │ ├── image_srcset_sizes.html │ ├── images/ │ │ ├── 220x280-01.avif │ │ ├── 220x280-02.avif │ │ ├── 220x280-03.avif │ │ ├── 220x280-04.avif │ │ ├── 220x280-05.avif │ │ ├── 220x280-06.avif │ │ ├── 220x280-07.avif │ │ ├── 220x280-08.avif │ │ ├── 220x280-09.avif │ │ ├── 220x280-10.avif │ │ ├── 220x280-11.avif │ │ ├── 220x280-12.avif │ │ ├── 220x280-13.avif │ │ ├── 220x280-14.avif │ │ ├── 220x280-15.avif │ │ ├── 220x280-16.avif │ │ ├── 220x280-17.avif │ │ ├── 220x280-18.avif │ │ ├── 220x280-19.avif │ │ ├── 220x280-20.avif │ │ ├── 220x280-21.avif │ │ ├── 220x280-22.avif │ │ ├── 220x280-23.avif │ │ ├── 220x280-24.avif │ │ ├── 220x280-25.avif │ │ ├── 220x280-26.avif │ │ ├── 220x280-27.avif │ │ ├── 220x280-28.avif │ │ ├── 220x280-29.avif │ │ ├── 220x280-30.avif │ │ ├── 220x280-31.avif │ │ ├── 220x280-32.avif │ │ ├── 220x280-33.avif │ │ ├── 220x280-34.avif │ │ ├── 220x280-35.avif │ │ ├── 220x280-36.avif │ │ ├── 220x280-37.avif │ │ ├── 220x280-38.avif │ │ ├── 220x280-39.avif │ │ ├── 220x280-40.avif │ │ ├── 440x560-01.avif │ │ ├── 440x560-02.avif │ │ ├── 440x560-03.avif │ │ ├── 440x560-04.avif │ │ ├── 440x560-05.avif │ │ ├── 440x560-06.avif │ │ ├── 440x560-07.avif │ │ ├── 440x560-08.avif │ │ ├── 440x560-09.avif │ │ ├── 440x560-10.avif │ │ ├── 440x560-11.avif │ │ ├── 440x560-12.avif │ │ ├── 440x560-13.avif │ │ ├── 440x560-14.avif │ │ ├── 440x560-15.avif │ │ ├── 440x560-16.avif │ │ ├── 440x560-17.avif │ │ ├── 440x560-18.avif │ │ ├── 440x560-19.avif │ │ ├── 440x560-20.avif │ │ ├── 440x560-21.avif │ │ ├── 440x560-22.avif │ │ ├── 440x560-23.avif │ │ ├── 440x560-24.avif │ │ ├── 440x560-25.avif │ │ ├── 440x560-26.avif │ │ ├── 440x560-27.avif │ │ ├── 440x560-28.avif │ │ ├── 440x560-29.avif │ │ ├── 440x560-30.avif │ │ ├── 440x560-31.avif │ │ ├── 440x560-32.avif │ │ ├── 440x560-33.avif │ │ ├── 440x560-34.avif │ │ ├── 440x560-35.avif │ │ ├── 440x560-36.avif │ │ ├── 440x560-37.avif │ │ ├── 440x560-38.avif │ │ ├── 440x560-39.avif │ │ └── 440x560-40.avif │ ├── lazily_load_lazyLoad.html │ ├── lazy_functions.html │ ├── load.html │ ├── load_all.html │ ├── native_lazyload.html │ ├── native_lazyload_conditional.html │ ├── object.html │ ├── picture_media.html │ ├── picture_type_avif.html │ ├── popup_layer.html │ ├── print.html │ ├── restore_destroy.html │ ├── sliders_css_only.html │ ├── swiper.html │ ├── thresholds.html │ ├── video.html │ └── video_autoplay.html ├── dist/ │ ├── esm/ │ │ ├── autoInitialize.js │ │ ├── callback.js │ │ ├── cancelOnExit.js │ │ ├── class.js │ │ ├── constants.js │ │ ├── counters.js │ │ ├── data.js │ │ ├── defaults.js │ │ ├── dom.js │ │ ├── elementStatus.js │ │ ├── environment.js │ │ ├── event.js │ │ ├── forEachSource.js │ │ ├── intersectionHandlers.js │ │ ├── intersectionObserver.js │ │ ├── lazyload.js │ │ ├── load.js │ │ ├── native.js │ │ ├── online.js │ │ ├── originalAttributes.js │ │ ├── reset.js │ │ ├── restore.js │ │ ├── set.js │ │ ├── tempImage.js │ │ └── unobserve.js │ ├── lazyload.iife.js │ └── lazyload.js ├── funding.yml ├── jest.config.cjs ├── package.json ├── playwright.config.js ├── rollup.config.mjs ├── src/ │ ├── autoInitialize.js │ ├── callback.js │ ├── cancelOnExit.js │ ├── class.js │ ├── constants.js │ ├── counters.js │ ├── data.js │ ├── defaults.js │ ├── dom.js │ ├── elementStatus.js │ ├── environment.js │ ├── event.js │ ├── forEachSource.js │ ├── intersectionHandlers.js │ ├── intersectionObserver.js │ ├── lazyload.js │ ├── load.js │ ├── native.js │ ├── online.js │ ├── originalAttributes.js │ ├── reset.js │ ├── restore.js │ ├── set.js │ ├── tempImage.js │ └── unobserve.js ├── tests/ │ ├── e2e/ │ │ ├── background_image.spec.js │ │ ├── background_image_multi.spec.js │ │ └── image_basic.spec.js │ └── unit/ │ ├── cancelOnExit.test.js │ ├── lib/ │ │ ├── expectExtend.js │ │ └── getFakeInstance.js │ ├── load.test.js │ ├── originalAttributes.test.js │ ├── reset.test.js │ ├── restore.test.js │ └── set.test.js ├── todo.md ├── typings/ │ └── lazyload.d.ts └── utils/ └── index.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "ignore": ["node_modules/**"], "presets": [["@babel/env", { "modules": false }]], "env": { "test": { "presets": ["@babel/preset-env"] } } } ================================================ FILE: .eslintignore ================================================ dist/**/*.js ================================================ FILE: .eslintrc.json ================================================ { "parserOptions": { "ecmaVersion": 6, "sourceType": "module" }, "env": { "browser": true, "jest": true }, "rules": { "quotes": [2, "double"], "block-scoped-var": 1, "class-methods-use-this": 1, "complexity": 1, "consistent-return": 1, "curly": 2, "default-case": 1, "dot-location": 1, "dot-notation": 1, "eqeqeq": 2, "guard-for-in": 1, "no-alert": 1, "no-caller": 1, "no-case-declarations": 1, "no-div-regex": 1, "no-else-return": 1, "no-empty-function": 1, "no-empty-pattern": 1, "no-eq-null": 1, "no-eval": 1, "no-extend-native": 1, "no-extra-bind": 1, "no-extra-label": 1, "no-fallthrough": 1, "no-floating-decimal": 1, "no-global-assign": 1, "no-implicit-coercion": 0, "no-implicit-globals": 1, "no-implied-eval": 1, "no-invalid-this": 1, "no-iterator": 1, "no-labels": 1, "no-lone-blocks": 1, "no-loop-func": 1, "no-magic-numbers": [1, { "ignore": [-1, 0, 1] }], "no-multi-spaces": 1, "no-multi-str": 1, "no-new": 1, "no-new-func": 1, "no-new-wrappers": 1, "no-octal": 1, "no-octal-escape": 1, "no-param-reassign": 1, "no-proto": 1, "no-redeclare": 1, "no-restricted-properties": 1, "no-return-assign": 1, "no-return-await": 1, "no-script-url": 1, "no-self-assign": 1, "no-self-compare": 1, "no-sequences": 1, "no-throw-literal": 1, "no-unmodified-loop-condition": 1, "no-unused-expressions": 1, "no-unused-labels": 1, "no-useless-call": 1, "no-useless-concat": 1, "no-useless-escape": 1, "no-useless-return": 1, "no-void": 1, "no-warning-comments": 1, "no-with": 1, "prefer-promise-reject-errors": 1, "radix": 1, "require-await": 1, "vars-on-top": 1, "wrap-iife": 1, "yoda": 1 } } ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp *.sln merge=union *.csproj merge=union *.vbproj merge=union *.fsproj merge=union *.dbproj merge=union # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain ================================================ FILE: .github/CONTRIBUTING.md ================================================ Thank you for taking the time to contribute! To report a bug or request an enhancement or a feature, use the [issues page](https://github.com/verlok/vanilla-lazyload/issues) on github If you just want to show your appreciation for this script, how about [buying a coffee](https://ko-fi.com/verlok) to its author? If you want to contribute actively with your own code, please: 1. fork the repo on your namespace 2. open a new branch 3. develop your contribution on the branch 4. create a pull request towards this repo I recommend to do **one pull request per feature** to make sure your contribution is easy to review and accept. Thank you and... may the force be with you! ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: 'TYPE: Bug' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **LazyLoad version** Please report which version of LazyLoad you're using. - Version [e.g. 8.x.x, 10.x.x, 11.x.x] **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Screenshots** If applicable, add screenshots to help explain your problem. **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: 'TYPE: Enhancement' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE/support-request.md ================================================ --- name: Support request about: Ask a question or support title: '' labels: 'TYPE: Question' assignees: '' --- **Is your support request related to a problem? Please describe.** A clear and concise description of what the problem is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **LazyLoad version** Please report which version of LazyLoad you're using. - Version [e.g. 8.x.x, 10.x.x, 11.x.x] **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Screenshots** If applicable, add screenshots to help explain your problem. **Additional context** Add any other context about the problem here. ================================================ FILE: .github/workflows/ci.yml ================================================ name: Node.js CI on: - push - pull_request jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: node-version: [lts/*, latest] os: [ubuntu-latest, macos-latest, windows-latest] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'npm' - run: npm install - run: npm run build --if-present - run: npm run test:unit ================================================ FILE: .github/workflows/playwright.yml ================================================ name: Playwright Tests on: push: branches: [main, master, develop, "feature/**"] pull_request: branches: [main, master] jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: lts/* - name: Install dependencies run: npm ci - name: Install Playwright Browsers run: npx playwright install --with-deps - name: Run Playwright tests run: npx playwright test - uses: actions/upload-artifact@v4 if: always() with: name: playwright-report path: playwright-report/ retention-days: 30 ================================================ FILE: .gitignore ================================================ Thumbs.db ehthumbs.db Desktop.ini $RECYCLE.BIN/ *.cab *.msi *.msm *.msp .DS_Store .AppleDouble .LSOverride Icon ._* .Spotlight-V100 .Trashes .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk .idea node_modules _site .sass-cache /test-results/ /playwright-report/ /blob-report/ /playwright/.cache/ coverage ================================================ FILE: .prettierrc ================================================ { "tabWidth": 2, "useTabs": false, "printWidth": 100, "trailingComma": "none" } ================================================ FILE: .vscode/settings.json ================================================ { "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, "editor.tabSize": 4, "editor.defaultFormatter": "esbenp.prettier-vscode" } ================================================ FILE: CHANGELOG.md ================================================ # CHANGELOG ## Version 19 #### 19.1.3 - File `CHANGELOG.md` is now included in the package and installed along with it - Updated Babel, Rollup and Playwright to the latest versions #### 19.1.2 - Modernized code and smaller file bundles, everything is now shipped as in the ES2015 format. #### 19.1.1 - Removed code to support image-set on legacy versions of Chromium browsers - Added coverage with 2 more demos for images with single src and a placeholder image #### 19.1.0 - Added end to end tests to expand test coverage to more use cases and cross browser #### 19.0.5 - Removed `.eslintrc.json`, `LICENSE`, `CHANGELOG.md`, `CODE_OF_CONDUCT.md`, `README.md`, `package.json` from the package files, as they didn't have any impact on [code quality](https://docs.npmjs.com/searching-for-and-choosing-packages-to-download#quality). #### 19.0.4 - Removed Babel plugin to polyfill `Object.assign()`, as suggested in #611. #### 19.0.3 - Files `.eslintrc.json`, `LICENSE`, `CHANGELOG.md`, `CODE_OF_CONDUCT.md`, `README.md`, `package.json` are now included in the package #### 19.0.1 - Restored compatibility for bundlers that used this module, see #609 #### 19.0.0 - Rollup setup enhancement - Refactored the Rollup setup to improve performance and maintainability. - Switched to ES6 module format for better compatibility and readability. - Enabled tree shaking and module preservation to optimize the build output. - Added [ESM demo](demos/esm.html) to showcase the functionality of the esm module. - Updated various dependencies to their latest versions for better compatibility and security - Removed AMD module from the `/dist` folder, as it's been unused since version 11 - Removed AMD-related demos ## Version 18 #### 18.0.0 - **Dropped support for Internet Explorer 11** - Modernized code - Smaller file ## Version 17 #### 17.9.0 - Allowing to pass empty string (`""`) as value for class options (`class_loading`, `class_applied`, `class_loaded`, `class_error`, `class_entered`, `class_exited`) so that no DOM mutation will happen if not necessary. This is a potential performance improvement. Suggested in [#605](https://github.com/verlok/vanilla-lazyload/issues/605). #### 17.8.8 - Fixed dependency issues detected by `npm audit` #### 17.8.5 - Improved callbacks check by introducing type check (must be `function`) #### 17.8.4 - Updated link to demos in the documentation. #### 17.8.3 - Fixed `callback_error` in background images demos, like suggested in #573. Thanks to @Soul244. #### 17.8.2 - Fixed a bug which occurred if the network connection went off and on again after a LazyLoad instance was destroyed #### 17.8.1 - Updated Typescript typings #### 17.8.0 - Added the ability to lazyload background images with CSS `image-set()` via `data-bg-set`. #### 17.7.0 - Added the new option `restore_on_error` to restore original attributes on error. #### 17.6.1 - Removed nasty "debugger" from code (sorry about that rookie mistake!) #### 17.6.0 - Added ability to lazily load the `` tag, as requested in #177. Useful to lazily load animated SVGs. #### 17.5.1 - Updated Typescript typings #### 17.5.0 - Added the ability to restore DOM to its original state through the `restoreAll()` method. - Destroy demo became [restore and destroy demo](demos/restore_destroy.html) #### 17.4.0 - Adding native lazy loading for videos, as discussed in #527. Thanks to @saschaeggi. - Updated the `native_lazyload_conditional.html` demo with the new best practice for cross browser native lazy loading. #### 17.3.2 - Fixes double trigger of `callback_load` after a watched image was loaded using the static `load()` method (#526). Thanks to @nick-vincent. #### 17.3.1 - Removed `caniuse-lite` from dependencies. Fixes #505. Thanks to @ar53n. #### 17.3.0 - Added `class_entered` and `class_exited` options to apply a class when an element entered and/or exited the viewport #### 17.2.0 - Rolling back the "`data` attribute cleanup" feature that was released on 16.1.0 and was causing issues like [#484](https://github.com/verlok/vanilla-lazyload/issues/484) when more than one instance of LazyLoad were working on the same elements of the page - the script is also 500 bytes lighter now #### 17.1.3 - Added missing types (#480), thanks to @ar53n (#482) #### 17.1.2 - Fixed TypeScript typings (#475), thanks to @ar53n (#477) #### 17.1.1 - Fixing npm audit vulnerabilities #### 17.1.0 - Unobserve all elements on `loadAll()` call. It's better for performance, and it solves #469. - Added some hidden images in the `load_all.html` demo #### 17.0.1 - Bug fix: `callback_exit()` was not being called on non-image elements (#468). #### 17.0.0 - The `elements_selector` option now defaults to `.lazy` (was `img`) - The `cancel_on_exit` option now defaults to `true` (was `false`) See [UPGRADE.md](UPGRADE.md) to understand **if** you are impacted by any breaking change and **how** to upgrade from previous versions. --- **Love this project? 😍 [Buy me a coffee!](https://ko-fi.com/verlok)** --- ## Version 16 #### 16.1.0 Improved speed, cleaning DOM, better working destroy, and also fixed 2 bugs. - Cleaning up `data` attributes from the DOM when finished using them (mainly when elements have finished loading) - Improved `destroy` method, which now also removes lazyload's additions to the DOM elements - Video elements are now only listening to the `loadeddata` event, no longer to `load` - Removed constants containing strings. I thought it would produced shorter minified code, but discovered that terser expands them to strings. - Bugfix: when lazily loading videos, the error `_poster_ is undefined` was thrown - Bugfix: when selecting native lazy loading, the `loading` class was added without knowing whether or not the loading had started #### 16.0.0 Functional changes: - Removed call to deprecated `callback_reveal` - Removed deprecated instance `load()` method in favor of the static `LazyLoad.load()` method - Replaced `auto_unobserve` with `unobserve_completed`, still defaulting to `true` - Introduced a new `unobserve_entered` option (useful to execute lazy functions once) - Created a demo called `lazy_functions.html` to show how to execute functions as elements enter the viewport - Wrote a new recipe to facilitate the lazy execution of scripts/functions - Renamed instance method `resetElementStatus()` to the static `LazyLoad.resetStatus()` - Removed the `load_delay` option since there's no more use for it - Removed the `load_delay` related demos See [UPGRADE.md](UPGRADE.md) to understand **if** you are impacted by any breaking change and **how** to upgrade from previous versions. Internal changes: - Simplified management of the `cancel_on_exit` with less increase/decrease of the `toLoadCount` property - Refactored counters functions in a new `lazyload.counters` file --- **Love this project? 😍 [Buy me a coffee!](https://ko-fi.com/verlok)** --- ## Version 15 #### 15.2.0 OPTIMIZE FOR SLOW CONNECTIONS WITH `cancel_on_exit` Want to optimize speed for users who scroll down fast on a slow connection? Just set `cancel_on_exit: true` and LazyLoad will cancel the download of images exiting the viewport while still loading, eventually restoring the original attributes. - Introduced the new `cancel_on_exit` option. - Introduced the `callback_cancel` option, just in case you want to perform any additional action whenever a download gets canceled by `cancel_on_exit`. - Created a new demo named `cancel_on_exit.html` so you can try the new `cancel_on_exit` option live. - Set `cancel_on_exit` to `true` in the following demos, so you can test how it behaves... - `image_ph_inline.html`, with an inline SVG placeholder - `image_ph_external.html`, with an external SVG placeholder - `delay_test.html`, in conjuction with the `delay_load` option - `fade_in.html`, with a beautiful fade-in effect. The `cancel_on_exit` option applies only to images so to the `img` (and `picture`) tags. It doesn't work for background images, `iframe`s nor `video`s. The `cancel_on_exit` option will probably default to `true` starting from the next major version, so give it a try! And please report your feedback in the comments of [#438](https://github.com/verlok/vanilla-lazyload/issues/438). API - Added the `resetElementStatus()` method for when you need to tell LazyLoad to consider an image (or other DOM element) again. This is particularly useful if you change the `data-src` attribute after the previous `data-src` was loaded). See the [API section](README.md#-api) in the README file for more information. FIX - The `callback_exit` callback was called several times (for every images out of the viewport) at instance creation or upon `update()` calls. Now the callback is properly called only when any element exits the viewport. INTERNALS - Improved script performance by reducing the number of event listeners used for loading elements. - Changed the values that the (internally used) `data-ll-status` attribute can take. Removed the status `"observed"` (it was useless) and introduced status `"delayed"`. #### 15.1.1 Fixed a bug when loading lazy background images on HiDPI screens, `data-bg-hidpi` was mandatory, not it fallbacks to `data-bg`. #430 #### 15.1.0 Lazy background images just gained support for hiDPI ("retina") screens! Place your standard resolution images in the `data-bg` attribute and your hiDPI images in `data-bg-hidpi`. Same for `data-bg-multi` and `data-bg-multi-hidpi`. #### 15.0.0 **Lazy background images gained loaded/error classes and callbacks! 🎉** **Breaking changes impacting lazy background images!** ⚠ See [UPGRADE.md](UPGRADE.md) to understand **if** you are impacted and **how** to upgrade from previous versions. - Lazy loading of **one background image** using the `data-bg` attribute, now manages the `load` and `error` events, so they are applied the classes defined in the `class_loading`/`class_loaded`/`class_error`, and the callbacks defined in `callback_loading`/`callback_loaded`/`callback_error`. - Lazy loading of **multiple background images** is still possible via the `data-bg-multi` attribute. In this case, the `load` and `error` events are not managed. The `class_applied` and `callback_applied` can be used to understand when the multiple background was applied to the element. - Updated background images demos: - background-images.html -> single background images - background-images-multi.html -> multiple background images - Added [UPGRADE.md](UPGRADE.md), a guide on how to upgrade from previous versions (from version 12 up) --- **Love this project? 😍 [Buy me a coffee!](https://ko-fi.com/verlok)** --- ## Version 14 #### 14.0.1 Fixed error TS1036: Statements are not allowed in ambient contexts. Closes #427 #### 14.0.0 🎉 **Major refactoring and performance improvement!** 🔍 File size stays tiny: only 2.07 KB gZipped **Settings** - `callback_loading` is called when an element started loading - `callback_reveal` is now **⚠ DEPRECATED, use `callback_loading` instead** (it's the same thing, it was just renamed). `callback_reveal` will be removed and will stop working in version 15. **Instance methods** - `update()` method now **also unobserves deleted elements**, instead of just looking for and observing new elements - `destroy()` **destroys better** than it did before, `delete`-ing properties instead of setting their values to `null` - `load()` method (as an instance method) is now **⚠ DEPRECATED, use the static method instead**. If you were using `aLazyLoadInstance.load(element)` you should change it to `LazyLoad.load(element, settings)`. **Static methods** - `load()` was added as a static method. Note that if you need to use custom settings, you need to pass them in the `settings` parameter. **Instance properties** - Added `toLoadCount`. It's the counter of the elements that haven't been lazyloaded yet. **DOM** - Removed the `data-was-processed` attribute, that was added to mark lazy DOM elements as "already managed". If you were manually handling that attribute to obtain some goal, this is a potentially breaking change. You should now refer to the `data-ll-status` instead. - Added the `data-ll-status` attribute, which is now used to mark the status of a lazy DOM element. The values it can take are: `observing` (not loaded yet), `loading` (loading started), `loaded` (load completed), `error` (an error has occured), `native` (similar to `observing`, but managed by native lazy loading). --- **Love this project? 😍 [Buy me a coffee!](https://ko-fi.com/verlok)** --- ## Version 13 #### 13.0.1 - Fixed a JS error that could happen to IE11 users after going offline and back online - Minor refactoring for better readibility and lighter code (and files)! #### 13.0.0 - Added the minified version of `dist/lazyload.esm.js` as `dist/lazyload.esm.min.js`, so now you can effortlessly use it with an ES module `import` statement when using `type="module"` - Reduced files weight even more! `dist/lazyload.iife.min.js` now weights only 2.03 KB GZipped - Removed the `callback_set` callback that was **deprecated** since version 11 in favour of `callback_reveal` - Removed sourcemaps (they were probably used only by the authors, but if anyone was actually needing them, we can bring them back) - Hidden the `_extends` function inside LazyLoad's scope (it was global before) - Updated build tooling: removed Gulp, now using Rollup & Babel only ## Version 12 #### 12.5.1 Restored IE 11 compatibility, which was broken since 12.2.0. See #414. Thanks to @ninosaurus for reporting. #### 12.5.0 The once-private `_loadingCount` property is now public and renamed to `loadingCount`. This property contains the number of images that are currently downloading from the network, limitedly to the ones managed by an instance of LazyLoad. This is particularly useful to understand whether or not is safe to destroy an instance of LazyLoad. See implementation in the [destroy demo](demos/destroy.html). Thanks to @wzhscript and @eugene-stativka. #### 12.4.0 Video `poster`s can now be loaded lazily, as requested in #365 #### 12.3.0 Callbacks now pass more arguments! `callback_enter`, `callback_exit` now pass: 1. the DOM element that entered / exited the viewport 2. the `IntersectionObserverEntry` that triggered the enter/exit event 3. the LazyLoad instance `callback_load`, `callback_error`, `callback_reveal` now pass 1. the DOM element that entered / exited the viewport 2. the LazyLoad instance `callback_finish` now passes: 1. the LazyLoad instance The README file has been updated accordingly. #### 12.2.0 Released new feature "retry when back online". Now if your users lose the internet connection causing errors on images loading, this script tries and loads those images again when the connection is restored. #### 12.1.1 Solved a bug with Internet Explorer 11 and the W3C polyfill, as reported in #383. #### 12.1.0 - Updated npm dev dependencies - Added the new `image_ph_inline.html`, with an inline SVG placeholder - Added the new `image_ph_external.html`, with an external SVG placeholder #### 12.0.3 Updated the IntersectionObserver polyfill to version 0.7.0 #### 12.0.2 Improved detection of browser support of IntersectionObserver, as suggested in #362. Thanks to @kaldonir #### 12.0.1 Updated CHANGELOG.md and README.md to mention the change of the option name `callback_load` which is called `callback_loaded` in versions 11.0.0 and above. #### 12.0.0 - Reorganized code - Improved native lazy loading demos - Aligned console messages throughout all demos. #### 12.0.0-beta.0 - Added the `use_native` option which enables _native lazy loading_ (where supported) with the `loading="lazy"` attribute. See #331 - Added two demos: - native_lazyload_conditional.html which you can use to test the `use_native` option - native_lazyload.html which always uses native lazy loading (without JS) just to test how it works beyond the LazyLoad script - Refactored the constructor and the `update` method ## Version 11 #### 11.0.6 Restored the `callback_set` callback as **deprecated**, in order to make the upgrade from v.10 easier. #### 11.0.5 Fixed the `module` property of this package.json, which was pointing to a non-existing dist file. #### 11.0.4 Fixed the `main` property of this package.json, which was pointing to a non-existing dist file. #### 11.0.3 Rollback of the patch applied in 11.0.2 since it gave strange results in some cases. See #293. Thanks to @davejamesmiller for the analysis and the report. #### 11.0.2 Applied a patch to resolve #293 a [Chromium bug](https://bugs.chromium.org/p/chromium/issues/detail?id=910741#c13) already fixed in Chrome 72. Thanks to @dverbovyi for the analysis and the report. #### 11.0.1 Squashed a nasty bug that occurred on IE 11 and Safari when the `IntersectionObserver` polyfill wasn't loaded before LazyLoad. #### 11.0.0 - Changed bundle file name of ES Module from `lazyload.es2015.js` to `lazyload.esm.js` - Removed the `to_webp` option (see issue #288) - Ceased support and development of LazyLoad v.8 (see issue #306) version. If you were using it, please update your code to use `callback_reveal` instead. - Private methods like `_setObserver`, `_onIntersection` etc. are now hidden from the outside. - Added the `auto_unobserve` boolean option, see API. - Bugfix: `loadAll()` didn't unobserve elements. - Updated to Jest 24, Babel 7, etc. - Fixed dev dependencies vulnerabilities - Updated callbacks. See below: Callbacks updated: - **Changed** `callback_enter`. This callback is now called whenever an element enters the viewport, even when `load_delay` is set. In previous versions, this callback was delayed until an element started loading if a `load_delay` was set. Note that this is a **possible breaking change**, which you can fix using `callback_reveal` instead. - **Renamed** `callback_loaded` is the new name of `callback_load`. - **Added** `callback_exit`. This callback is called whenever an element exits the viewport, even if a `load_delay` is set. - **Added** `callback_reveal`. This callback is called just after an element starts loading. - **Deprecated** `callback_set`. This callback still works\*, but will be removed in the next major \* it didn't work from versions 11.0.0 to 11.0.5, it still works from 11.0.6. ## Version 10 #### 10.20.1 Fixed a bug for which LazyLoad didn't copy the `data-sizes` attribute value to `sizes` in `source` tags inside `picture`. See #307. #### 10.20.0 Improved WebP detection to work correctly on Firefox too, see #298. Thanks to @ipernet for contributing. #### 10.19.1 - Fixed build for those using React + SSR, see #287 - TypeScript definitions clearified, see #283 - Gulp updated to v.4.0.0 to make it work with node 10 Thanks to @AlexCSR and @muturgan for contributing. #### 10.19.0 - Added the ability to know when all images have been downloaded through the `callback_finish` callback. - Added the file `demos/print.html` to demo how to print lazy images. #### 10.18.0 Added the ability to have multiple background images, through the new `data_bg` option. #### 10.17.0 Added the ability to set different thresholds for the scrolling area, through the new `thresholds` option. #### 10.16.2 **BUGFIX**: Class `loaded` was not applied to a loaded video (issue #239). #### 10.16.1 **BUGFIX**: Autoplaying video not loaded correctly after entering the viewport (issue #240). Thanks to @maeligg. #### 10.16.0 Added new option `load_delay` to skip loading when fast scrolling occurs, as requested in issues #235 and #166. Pass in a number of milliseconds, and each image will be loaded after it stayed inside that viewport for that time. #### 10.15.0 - Refactorized code & improved script performance - **BUGFIX**: Fixed webpack import (issue #230) `TypeError: default is not a constructor` #### 10.14.0 Now supporting WebP through dynamic extension rename if the user browser is compatible. #### 10.13.0 - Shortened the RegEx for crawlers detection (shaved a few bytes) - Released LazyLoad in new module types! Enjoy the new flavours :) | Filename | Module Type | Advantages | | ---------------------- | ---------------------------------------------- | ------------------------------------------------------------------ | | `lazyload.min.js` | UMD (Universal Module Definition) | Works pretty much everywhere, even in common-js contexts | | `lazyload.iife.min.js` | IIFE (Immediately Invoked Function Expression) | Works as in-page ` ``` OR, if you prefer to import it as an ES module: ```html ``` Then, in your javascript code: ```js var lazyLoadInstance = new LazyLoad({ // Your custom settings go here }); ``` To be sure that DOM for your lazy content is ready when you instantiate LazyLoad, **place the script tag right before the closing `` tag**. If more DOM arrives later, e.g. via an AJAX call, you'll need to call `lazyLoadInstance.update();` to make LazyLoad check the DOM again. ```js lazyLoadInstance.update(); ``` ### Using an `async` script If you prefer, it's possible to include LazyLoad's script using `async` script and initialize it as soon as it's loaded. To do so, **you must define the options before including the script**. You can pass: - `{}` an object to get a single instance of LazyLoad - `[{}, {}]` an array of objects to get multiple instances of LazyLoad, each one with different options. ```html ``` Then include the script. ```html ``` **Possibly place the script tag right before the closing `` tag**. If you can't do that, LazyLoad could be executed before the browser has loaded all the DOM, and you'll need to call its `update()` method to make it check the DOM again. ### Using an `async` script + getting the instance reference Same as above, but you must put the `addEventListener` code shown below before including the `async` script. ```html ``` Then include the script. ```html ``` Now you'll be able to call its methods, like: ```js lazyLoadInstance.update(); ``` [DEMO](https://verlok.github.io/vanilla-lazyload/demos/async.html) - [SOURCE](https://github.com/verlok/vanilla-lazyload/blob/master/demos/async.html) ← for a single LazyLoad instance [DEMO](https://verlok.github.io/vanilla-lazyload/demos/async_multiple.html) - [SOURCE](https://github.com/verlok/vanilla-lazyload/blob/master/demos/async_multiple.html) ← for multiple LazyLoad instances ### Local install If you prefer to install LazyLoad locally in your project, you can! #### Using npm ``` npm install vanilla-lazyload ``` #### Using bower ``` bower install vanilla-lazyload ``` #### Manual download Download one the latest [releases](https://github.com/verlok/vanilla-lazyload/releases/). The files you need are inside the `dist` folder. If you don't know which one to pick, use `lazyload.min.js`, or read [about bundles](#bundles). ### Local usage Should you install LazyLoad locally, you can import it as ES module like the following: ```js import LazyLoad from "vanilla-lazyload"; ``` It's also possible (but unadvised) to use the `require` commonJS syntax. More information about bundling LazyLoad with WebPack are available on [this specific repo](https://github.com/verlok/lazyload-es2015-webpack-test). ### Usage with React Take a look at this example of [usage of React with LazyLoad](https://codesandbox.io/s/20306yk96p) on Sandbox. This implementation takes the same props that you would normally pass to the `img` tag, but it renders a lazy image. Feel free to fork and improve it! ### Bundles Inside the `dist` folder you will find different bundles. | Filename | Module Type | Advantages | | ---------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `lazyload.min.js` | UMD (Universal Module Definition) | Works pretty much everywhere, even in common-js contexts | | `lazyload.iife.min.js` | IIFE (Immediately Invoked Function Expression) | Works as in-page ` ================================================ FILE: demos/async_multiple.html ================================================ Async loading, multiple instances - Lazyload demos

Async loading, multiple instances demo

  • Stivaletti
  • Open toe
  • Sneakers & Tennis
  • Sneakers & Tennis shoes basse
  • Sneakers & Tennis shoes alte
  • Sneakers & Tennis shoes alte
  • Sneakers & Tennis shoes basse
  • Sneakers & Tennis shoes basse
  • Stivali
  • Stivali
  • Stivaletti
  • Stivaletti
  • Stivali
  • Stivaletti
  • Décolleté
  • Mocassini
  • Stivaletti
  • Décolleté
  • Décolleté
  • Francesine
  • Stivaletti
  • Décolleté
  • Mocassini
  • Mocassini
  • Stivali
  • Stivaletti
  • Stivaletti
  • Mocassini
  • Stivaletti
  • Stivaletti
  • Stivaletti
  • Stivaletti
  • Cuissardes
  • Décolleté
  • Stivaletti
  • Sneakers & Tennis shoes basse
  • Mocassini
  • Sneakers & Tennis shoes basse
  • Mocassini
  • Mocassini
================================================ FILE: demos/background_images.html ================================================ Background images - Lazyload demos

Background images demo

================================================ FILE: demos/background_images_image-set.html ================================================ Background images with "image-set" - Lazyload demos

Background images with image-set demo

================================================ FILE: demos/background_images_multi.html ================================================ Multiple background images - Lazyload demos

Multiple background images demo

================================================ FILE: demos/container_multiple.html ================================================ Multiple scrolling container - Lazyload demos

Multiple scrolling container demo

  • Stivaletti
  • Open toe
  • Sneakers & Tennis
  • Sneakers & Tennis shoes basse
  • Sneakers & Tennis shoes alte
  • Sneakers & Tennis shoes alte
  • Sneakers & Tennis shoes basse
  • Sneakers & Tennis shoes basse
  • Stivali
  • Stivali
  • Stivaletti
  • Stivaletti
  • Stivali
  • Stivaletti
  • Décolleté
  • Mocassini
  • Stivaletti
  • Décolleté
  • Décolleté
  • Francesine
  • Stivaletti
  • Décolleté
  • Mocassini
  • Mocassini
  • Stivali
  • Stivaletti
  • Stivaletti
  • Mocassini
  • Stivaletti
  • Stivaletti
  • Stivaletti
  • Stivaletti
  • Cuissardes
  • Décolleté
  • Stivaletti
  • Sneakers & Tennis shoes basse
  • Mocassini
  • Sneakers & Tennis shoes basse
  • Mocassini
  • Mocassini
================================================ FILE: demos/container_single.html ================================================ Single scrolling container - Lazyload demos

Single scrolling container demo

================================================ FILE: demos/dynamic_content.html ================================================ Dynamic content - Lazyload demos

Dynamic content demo

================================================ FILE: demos/dynamic_content_nodeset.html ================================================ Dynamic content passing nodeset - Lazyload demos

Dynamic content passing nodeset demo

================================================ FILE: demos/embedded.html ================================================ Basic - Lazyload demos

Embedded in iframes

================================================ FILE: demos/error_no_restore.html ================================================ Error, no restore - Lazyload demos

Error, no restore

================================================ FILE: demos/error_restore.html ================================================ Error, restore - Lazyload demos

Error, restore original attribute

================================================ FILE: demos/esm.html ================================================ ESM version - Lazyload demos

ESM version demo

================================================ FILE: demos/fade_in.html ================================================ Fade in effect - Lazyload demos

Fade in effect demo

================================================ FILE: demos/hundreds.html ================================================ Hundreds of items - Lazyload demos

Hundreds of items demo

================================================ FILE: demos/iframes/i01.html ================================================

iframe 01

Hello, I'm in iframe

Lazily loaded

================================================ FILE: demos/iframes/i02.html ================================================

iframe 02

Hello, I'm in iframe

Lazily loaded

================================================ FILE: demos/iframes/i03.html ================================================

iframe 03

Hello, I'm in iframe

Lazily loaded

================================================ FILE: demos/iframes.html ================================================ Lazy iframes - Lazyload demos

Lazy iframes demo

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

Scroll down to see lazily loaded iframes

================================================ FILE: demos/image_basic.html ================================================ Basic - Lazyload demos

Basic demo

================================================ FILE: demos/image_hidden.html ================================================ Hidden images - Lazyload demos

Hidden images demo

================================================ FILE: demos/image_no_classes.html ================================================ Basic - Lazyload demos

Basic demo

================================================ FILE: demos/image_ph_external.html ================================================ Images with external placeholder - Lazyload demos

Images with external placeholder demo

================================================ FILE: demos/image_ph_inline.html ================================================ Images with inline placeholder - Lazyload demos

Images with inline placeholder demo

================================================ FILE: demos/image_srcset.html ================================================ Images with srcset without sizes - Lazyload demos

Images with srcset without sizes demo

================================================ FILE: demos/image_srcset_lazy_sizes.html ================================================ Images with srcset and lazy sizes - Lazyload demos

Images with srcset and lazy sizes demo

================================================ FILE: demos/image_srcset_sizes.html ================================================ Images with srcset and eager sizes - Lazyload demos

Images with srcset and eager sizes demo

================================================ FILE: demos/lazily_load_lazyLoad.html ================================================ Lazyly load lazyload - Lazyload demos

Lazyly load lazyload demo

Row 01, col 01
Lorem ipsum 01
Row 01, col 02
Lorem ipsum 02
Row 01, col 03
Lorem ipsum 03
Row 01, col 04
Lorem ipsum 04
Row 01, col 05
Lorem ipsum 05
Row 01, col 06
Lorem ipsum 06
Row 01, col 07
Lorem ipsum 07
Row 01, col 08
Lorem ipsum 08
Row 01, col 09
Lorem ipsum 09
Row 01, col 10
Lorem ipsum 10
Row 01, col 11
Lorem ipsum 11
Row 01, col 12
Lorem ipsum 12
Row 01, col 13
Lorem ipsum 13
Row 01, col 14
Lorem ipsum 14
Row 01, col 15
Lorem ipsum 15
Row 02, col 01
Lorem ipsum 01
Row 02, col 02
Lorem ipsum 02
Row 02, col 03
Lorem ipsum 03
Row 02, col 04
Lorem ipsum 04
Row 02, col 05
Lorem ipsum 05
Row 02, col 06
Lorem ipsum 06
Row 02, col 07
Lorem ipsum 07
Row 02, col 08
Lorem ipsum 08
Row 02, col 09
Lorem ipsum 09
Row 02, col 10
Lorem ipsum 10
Row 02, col 11
Lorem ipsum 11
Row 02, col 12
Lorem ipsum 12
Row 02, col 13
Lorem ipsum 13
Row 02, col 14
Lorem ipsum 14
Row 02, col 15
Lorem ipsum 15
Row 03, col 01
Lorem ipsum 01
Row 03, col 02
Lorem ipsum 02
Row 03, col 03
Lorem ipsum 03
Row 03, col 04
Lorem ipsum 04
Row 03, col 05
Lorem ipsum 05
Row 03, col 06
Lorem ipsum 06
Row 03, col 07
Lorem ipsum 07
Row 03, col 08
Lorem ipsum 08
Row 03, col 09
Lorem ipsum 09
Row 03, col 10
Lorem ipsum 10
Row 03, col 11
Lorem ipsum 11
Row 03, col 12
Lorem ipsum 12
Row 03, col 13
Lorem ipsum 13
Row 03, col 14
Lorem ipsum 14
Row 03, col 15
Lorem ipsum 15
Row 04, col 01
Lorem ipsum 01
Row 04, col 02
Lorem ipsum 02
Row 04, col 03
Lorem ipsum 03
Row 04, col 04
Lorem ipsum 04
Row 04, col 05
Lorem ipsum 05
Row 04, col 06
Lorem ipsum 06
Row 04, col 07
Lorem ipsum 07
Row 04, col 08
Lorem ipsum 08
Row 04, col 09
Lorem ipsum 09
Row 04, col 10
Lorem ipsum 10
Row 04, col 11
Lorem ipsum 11
Row 04, col 12
Lorem ipsum 12
Row 04, col 13
Lorem ipsum 13
Row 04, col 14
Lorem ipsum 14
Row 04, col 15
Lorem ipsum 15
Row 05, col 01
Lorem ipsum 01
Row 05, col 02
Lorem ipsum 02
Row 05, col 03
Lorem ipsum 03
Row 05, col 04
Lorem ipsum 04
Row 05, col 05
Lorem ipsum 05
Row 05, col 06
Lorem ipsum 06
Row 05, col 07
Lorem ipsum 07
Row 05, col 08
Lorem ipsum 08
Row 05, col 09
Lorem ipsum 09
Row 05, col 10
Lorem ipsum 10
Row 05, col 11
Lorem ipsum 11
Row 05, col 12
Lorem ipsum 12
Row 05, col 13
Lorem ipsum 13
Row 05, col 14
Lorem ipsum 14
Row 05, col 15
Lorem ipsum 15
Row 06, col 01
Lorem ipsum 01
Row 06, col 02
Lorem ipsum 02
Row 06, col 03
Lorem ipsum 03
Row 06, col 04
Lorem ipsum 04
Row 06, col 05
Lorem ipsum 05
Row 06, col 06
Lorem ipsum 06
Row 06, col 07
Lorem ipsum 07
Row 06, col 08
Lorem ipsum 08
Row 06, col 09
Lorem ipsum 09
Row 06, col 10
Lorem ipsum 10
Row 06, col 11
Lorem ipsum 11
Row 06, col 12
Lorem ipsum 12
Row 06, col 13
Lorem ipsum 13
Row 06, col 14
Lorem ipsum 14
Row 06, col 15
Lorem ipsum 15
Row 07, col 01
Lorem ipsum 01
Row 07, col 02
Lorem ipsum 02
Row 07, col 03
Lorem ipsum 03
Row 07, col 04
Lorem ipsum 04
Row 07, col 05
Lorem ipsum 05
Row 07, col 06
Lorem ipsum 06
Row 07, col 07
Lorem ipsum 07
Row 07, col 08
Lorem ipsum 08
Row 07, col 09
Lorem ipsum 09
Row 07, col 10
Lorem ipsum 10
Row 07, col 11
Lorem ipsum 11
Row 07, col 12
Lorem ipsum 12
Row 07, col 13
Lorem ipsum 13
Row 07, col 14
Lorem ipsum 14
Row 07, col 15
Lorem ipsum 15
Row 08, col 01
Lorem ipsum 01
Row 08, col 02
Lorem ipsum 02
Row 08, col 03
Lorem ipsum 03
Row 08, col 04
Lorem ipsum 04
Row 08, col 05
Lorem ipsum 05
Row 08, col 06
Lorem ipsum 06
Row 08, col 07
Lorem ipsum 07
Row 08, col 08
Lorem ipsum 08
Row 08, col 09
Lorem ipsum 09
Row 08, col 10
Lorem ipsum 10
Row 08, col 11
Lorem ipsum 11
Row 08, col 12
Lorem ipsum 12
Row 08, col 13
Lorem ipsum 13
Row 08, col 14
Lorem ipsum 14
Row 08, col 15
Lorem ipsum 15
Row 09, col 01
Lorem ipsum 01
Row 09, col 02
Lorem ipsum 02
Row 09, col 03
Lorem ipsum 03
Row 09, col 04
Lorem ipsum 04
Row 09, col 05
Lorem ipsum 05
Row 09, col 06
Lorem ipsum 06
Row 09, col 07
Lorem ipsum 07
Row 09, col 08
Lorem ipsum 08
Row 09, col 09
Lorem ipsum 09
Row 09, col 10
Lorem ipsum 10
Row 09, col 11
Lorem ipsum 11
Row 09, col 12
Lorem ipsum 12
Row 09, col 13
Lorem ipsum 13
Row 09, col 14
Lorem ipsum 14
Row 09, col 15
Lorem ipsum 15
Row 10, col 01
Lorem ipsum 01
Row 10, col 02
Lorem ipsum 02
Row 10, col 03
Lorem ipsum 03
Row 10, col 04
Lorem ipsum 04
Row 10, col 05
Lorem ipsum 05
Row 10, col 06
Lorem ipsum 06
Row 10, col 07
Lorem ipsum 07
Row 10, col 08
Lorem ipsum 08
Row 10, col 10
Lorem ipsum 09
Row 10, col 10
Lorem ipsum 10
Row 10, col 11
Lorem ipsum 11
Row 10, col 12
Lorem ipsum 12
Row 10, col 13
Lorem ipsum 13
Row 10, col 14
Lorem ipsum 14
Row 10, col 15
Lorem ipsum 15
================================================ FILE: demos/lazy_functions.html ================================================ Lazy functions - Lazyload demos

Lazy functions demo

Lorem ipsum dolor sit amet consectetur adipisicing elit. Nam quisquam laboriosam soluta veniam! Beatae tempora dicta voluptate consequuntur, nesciunt sequi alias quidem blanditiis obcaecati praesentium aut sint nobis aliquid ullam!

Lorem ipsum dolor sit, amet consectetur adipisicing elit. Illum cum unde aliquid ad quidem hic temporibus obcaecati quas sunt! Eius nisi ex ipsa suscipit aliquid velit sequi exercitationem itaque fuga!

Lorem ipsum dolor, sit amet consectetur adipisicing elit. Optio, quo, aliquid adipisci rerum, in quas ratione cupiditate sapiente iure aliquam molestiae provident maiores qui odit. Aut placeat deserunt libero quod?

Lorem ipsum dolor sit amet consectetur adipisicing elit. Illum deleniti dolorem hic autem at rem error non quos pariatur fugit. Distinctio est accusamus exercitationem nesciunt. Molestias blanditiis temporibus asperiores impedit!

Lorem ipsum dolor, sit amet consectetur adipisicing elit. Deleniti, aut ea. Perferendis facere tempore nemo eos illo consequatur! Ad quibusdam quis porro ipsam praesentium tenetur totam? Eum exercitationem laudantium praesentium!

Lorem ipsum dolor sit, amet consectetur adipisicing elit. Dolorum, alias omnis eveniet maxime rem nulla sed modi et, fugit eius ut praesentium exercitationem incidunt accusamus excepturi a laudantium fugiat id.

Lorem ipsum dolor sit amet consectetur adipisicing elit. Illum, fuga dignissimos. Nihil provident est quasi harum id sint enim officiis maxime facere incidunt quibusdam ab, magni iusto veritatis doloremque expedita.

Lorem, ipsum dolor sit amet consectetur adipisicing elit. Voluptatum numquam ipsam harum voluptates? Excepturi laboriosam quasi saepe. Quia officiis odit minus cumque, corporis, incidunt harum quod fuga placeat optio deserunt?

Lorem ipsum dolor sit amet consectetur adipisicing elit. Architecto exercitationem, dolorum quibusdam nihil nisi vitae veritatis recusandae nostrum cum enim iste unde fugiat esse voluptatum, voluptatem magnam, repudiandae veniam. Doloribus.

Lorem ipsum dolor sit amet consectetur adipisicing elit. Placeat similique necessitatibus minima atque sed debitis, sunt quasi esse consequatur qui, facilis temporibus voluptate eaque labore. Distinctio et accusantium doloremque officia.

Script execution here

Cumque explicabo iusto voluptates nostrum dolor tempore et sequi doloremque aspernatur id cum soluta rerum debitis mollitia numquam, officiis molestias optio? Placeat tempora porro odit expedita a ipsam provident atque!

Id, alias reiciendis animi incidunt quibusdam beatae neque ipsum illum inventore commodi, sunt similique labore at hic tempora. Sed est necessitatibus ea! Repellendus, nemo fugit pariatur soluta omnis modi explicabo!

Quas quia illo harum qui distinctio earum ullam. Est incidunt temporibus, repellendus rem fugiat dolor sit inventore! Soluta illum, minus totam autem fugiat recusandae eveniet sed ipsam officia ullam modi.

Minima eveniet quisquam quaerat natus nam voluptatibus eum fuga, aliquid doloribus reiciendis vero culpa eaque eligendi omnis delectus, dolores commodi tenetur optio eos temporibus sapiente accusantium. Maxime similique odit iusto!

Quae quas explicabo mollitia neque at error molestiae debitis nisi, iusto eum repellat doloribus sequi voluptatibus numquam quisquam quidem alias quibusdam enim? Id illo veritatis inventore quo recusandae esse vero.

Cum suscipit quae, rem dolor corrupti commodi placeat atque numquam? Perferendis, modi? Explicabo ad quisquam in id corporis labore. Reiciendis dolore magni voluptas ex, iste officia eius accusamus facilis cumque.

Hic dignissimos voluptate fugit officia atque reiciendis cumque explicabo impedit esse aperiam nesciunt beatae quibusdam illum unde facere, repellat ipsa optio nostrum distinctio consectetur numquam. Eos delectus quaerat fugiat laboriosam!

Temporibus, corrupti fuga. Suscipit, quaerat laborum quod quos nisi quam quia earum cupiditate cumque sed facilis, rerum modi soluta, ex placeat doloribus! Tempora voluptates consequuntur distinctio eius modi amet qui!

Consectetur esse illo excepturi numquam voluptas temporibus voluptatem distinctio ipsa nisi molestiae sint aliquam cumque quod sunt officiis, quibusdam a, beatae eum, suscipit obcaecati? Ut non beatae corporis a quo.

Facere blanditiis minus laudantium alias explicabo assumenda tempora? Rerum eos, quo in animi cupiditate eaque, vel corporis aut beatae distinctio labore dignissimos, adipisci deserunt esse voluptatibus. Facere nemo sit fugiat.

Script execution here

Quo at deserunt hic molestiae. Veritatis vero labore consequatur, est, eaque nostrum nesciunt velit, praesentium corporis obcaecati exercitationem voluptatem tempore. Quae quod mollitia fugit iusto saepe veritatis qui ipsum magni?

Modi tenetur aperiam laudantium eveniet rem, soluta in fugit earum vero unde ipsam cupiditate ducimus fuga hic velit libero temporibus maiores voluptatem adipisci. Inventore ducimus expedita numquam ea ex deserunt!

Quisquam quam molestiae nemo veniam mollitia doloremque modi rerum tempore voluptate at consequatur praesentium rem consequuntur eos provident tenetur quibusdam, et iste similique. Delectus deleniti et, eaque illum itaque hic!

Quos, voluptates esse recusandae consequuntur omnis eaque explicabo expedita voluptate ea perferendis, porro hic soluta nulla dolore. Excepturi accusamus reprehenderit temporibus explicabo quo sint sed, nobis dolores, velit alias nostrum.

Culpa hic quasi modi similique voluptatum cumque beatae nulla autem esse mollitia natus nihil aspernatur sed magnam, expedita est recusandae quisquam. Cumque accusamus ad porro necessitatibus in adipisci nihil doloribus.

Atque modi possimus aliquid eum consequuntur. Pariatur, blanditiis autem? Placeat repudiandae eius dolorum vero iusto ad fugit doloremque molestiae eum qui necessitatibus deserunt blanditiis illum, ea, expedita corporis odit vel.

Vero obcaecati explicabo iure aperiam aut modi architecto consectetur voluptatibus eos. Deleniti, temporibus! Aliquid dignissimos a, pariatur, voluptates hic dolores cupiditate atque ratione aliquam voluptatum accusantium corrupti tempora, doloribus incidunt!

Nostrum hic deleniti tempora sunt laudantium ullam deserunt ut et vero recusandae! Nam at, voluptatibus unde eveniet sunt reprehenderit tempore ut consequuntur eum dolores harum, earum assumenda, eos eius mollitia!

Labore explicabo voluptas assumenda ratione? Amet sunt deleniti nulla minima ipsam vel odit reprehenderit soluta et iste adipisci, minus illo, ea enim error debitis quidem consequuntur quia! Tenetur, dolorem modi.

Voluptates eligendi tempore nostrum, quasi veritatis similique quibusdam consectetur voluptatibus adipisci aliquid laborum dignissimos tempora excepturi soluta atque harum recusandae eum iste enim maiores. Nesciunt tenetur error quisquam voluptatem amet?

Script execution here

Eligendi aspernatur itaque esse nisi! Illo consequatur repellat, eius, laborum hic corporis ad voluptates nesciunt quo maiores aut itaque animi odit ipsum deserunt minima architecto maxime at quas repellendus debitis.

Sapiente, ipsum incidunt doloremque ipsam ullam laborum distinctio nemo facere voluptatum saepe reiciendis iure tenetur voluptatibus modi adipisci! Pariatur possimus dolores labore itaque autem dolore, accusantium omnis saepe incidunt veritatis?

Natus impedit eaque similique sint maiores? Cum dolores, non molestiae vitae expedita inventore harum corporis porro iusto possimus ullam dolor, autem fugit earum deserunt eum in quos commodi a sunt.

Architecto repellat nemo veritatis natus modi quod laudantium nulla quidem ipsa, inventore, vitae reiciendis excepturi! Excepturi, esse ullam atque unde facilis doloremque ipsum et aperiam ipsa facere! Esse, quibusdam odit!

Eveniet facere, sit dolorem natus maxime quasi voluptate possimus, temporibus maiores doloribus laborum minus neque nam itaque voluptatibus. Eum nihil aperiam eos voluptatem sunt natus quos cum expedita sapiente ea!

Voluptate nobis, dicta commodi ut id iure! Consequatur blanditiis aut ut quasi amet magni tempore, natus repellat dolorem repudiandae, recusandae voluptates eveniet vitae, officia non aspernatur ratione tempora distinctio porro?

Cum consectetur voluptatem natus odit alias, a dolore dicta fugit sunt amet dolores repudiandae eaque facilis iure quae minima, voluptas praesentium ipsam! Praesentium iusto soluta alias dolore amet minus odio?

Hic labore assumenda officiis ducimus iure aperiam at maiores natus quas quia nostrum, enim eaque soluta, repellat dolore est quidem ab adipisci suscipit nesciunt doloremque! Et expedita quis veniam dolore.

Harum, aliquid? Repellat, necessitatibus? Pariatur veniam atque vel dolorem fugiat ad reiciendis, voluptate quia provident laborum incidunt minus dolorum nemo necessitatibus earum, omnis ullam consequatur! Accusantium fuga animi incidunt nesciunt.

Quisquam a nulla veniam labore, quasi ipsa numquam saepe sint ratione ea. Doloribus quos molestias vitae earum repellat corrupti ad corporis at, nam nobis officia dolores magni illo fugiat a.

Script execution here
================================================ FILE: demos/load.html ================================================ Static load method - LazyLoad demos

Static load method demo

01a 01b

Product 01

02a 02b

Product 02

03a 03b

Product 03

04a 04b

Product 04

05a 05b

Product 05

06a 06b

Product 06

07a 07b

Product 07

08a 08b

Product 08

09a 09b

Product 09

10a 10b

Product 10

11a 11b

Product 11

12a 12b

Product 12

13a 13b

Product 13

14a 14b

Product 14

15a 15b

Product 15

16a 16b

Product 16

================================================ FILE: demos/load_all.html ================================================ Load all - Lazyload demos

Load all demo

================================================ FILE: demos/native_lazyload.html ================================================ Native lazy loading - Lazyload demos

Native lazy loading demo

You can use this page to test browsers' implementations of native lazy loading.
Please note that not even one line of JavaScript code is involved here.

================================================ FILE: demos/native_lazyload_conditional.html ================================================ Conditional native lazy loading - Lazyload demos

Conditional native lazy loading demo

Images

Iframes

Preload = none

Preload = metadata

================================================ FILE: demos/object.html ================================================ Lazy object - Lazyload demos

Lazy objects demo

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

Scroll down to see lazily loaded objects

================================================ FILE: demos/picture_media.html ================================================ Picture with media attribute in source tags - Lazyload demos

Picture with media attribute in source tags demo

================================================ FILE: demos/picture_type_avif.html ================================================ Picture with type attribute in source to conditionally load AVIF - Lazyload demos

Picture with type attribute in source to conditionally load AVIF demo

================================================ FILE: demos/popup_layer.html ================================================ Popup layer - Lazyload demos

Popup layer demo

================================================ FILE: demos/print.html ================================================ Print - Lazyload demos

Print demo

================================================ FILE: demos/restore_destroy.html ================================================ Restore and Destroy - Lazyload demos

Restore and Destroy demo

================================================ FILE: demos/sliders_css_only.html ================================================ Swipey CSS only - Lazyload demos

Swipey, CSS only

First slider

Stivaletti
Stivaletti
Stivaletti
Stivaletti
Stivaletti

Second slider

Stivaletti
Stivaletti
Stivaletti
Stivaletti
Stivaletti

Another slider

Stivaletti
Stivaletti
Stivaletti
Stivaletti
Stivaletti

Yet another slider

Stivaletti
Stivaletti
Stivaletti
Stivaletti
Stivaletti

And yet another yet another slider

Stivaletti
Stivaletti
Stivaletti
Stivaletti
Stivaletti
================================================ FILE: demos/swiper.html ================================================ Swiper lazily initialized - Lazyload demos

Swiper lazily initialized

This is an eagerly loaded swiper

Stivaletti
Stivaletti
Stivaletti
Stivaletti
Stivaletti

Following, more lazily loaded swipers

Stivaletti
Stivaletti
Stivaletti
Stivaletti
Stivaletti

Another lazy swiper

Stivaletti
Stivaletti
Stivaletti
Stivaletti
Stivaletti

Yet another lazy swiper

Stivaletti
Stivaletti
Stivaletti
Stivaletti
Stivaletti

And yet another yet another lazy swiper

Stivaletti
Stivaletti
Stivaletti
Stivaletti
Stivaletti
================================================ FILE: demos/thresholds.html ================================================ Threshold and thresholds - Lazyload demos

Threshold and thresholds demo

  • Stivaletti
  • Open toe
  • Sneakers & Tennis
  • Sneakers & Tennis shoes basse
  • Sneakers & Tennis shoes alte
  • Sneakers & Tennis shoes alte
  • Sneakers & Tennis shoes basse
  • Sneakers & Tennis shoes basse
  • Stivali
  • Stivali
  • Stivaletti
  • Stivaletti
  • Francesine
  • Stivaletti
  • Décolleté
  • Mocassini
  • Mocassini
  • Stivali
  • Stivaletti
  • Stivaletti
  • Mocassini
  • Stivaletti
  • Stivaletti
  • Stivaletti
  • Stivaletti
  • Cuissardes
  • Décolleté
  • Stivaletti
  • Sneakers & Tennis shoes basse
  • Mocassini
  • Sneakers & Tennis shoes basse
  • Mocassini
  • Mocassini
  • Stivali
  • Stivaletti
  • Décolleté
  • Mocassini
  • Stivaletti
  • Décolleté
  • Décolleté
================================================ FILE: demos/video.html ================================================ Lazy video - Lazyload demos

Lazy video demo - NO autoplay

👇🏼 Scroll down to see the lazy videos 👇🏼

Preload = none

Preload = metadata

Preload = auto

================================================ FILE: demos/video_autoplay.html ================================================ Lazy autoplay video - Lazyload demos

Lazy video demo, with autoplay

👇🏼 Scroll down to see the lazy videos 👇🏼

Preload = none

Preload = metadata

Preload = auto

================================================ FILE: dist/esm/autoInitialize.js ================================================ const t=function(t,e){let n;const i="LazyLoad::Initialized",o=new t(e);try{n=new CustomEvent(i,{detail:{instance:o}})}catch(t){n=document.createEvent("CustomEvent"),n.initCustomEvent(i,!1,!1,{instance:o})}window.dispatchEvent(n)},e=(e,n)=>{if(n)if(n.length)for(let i,o=0;i=n[o];o+=1)t(e,i);else t(e,n)};export{e as autoInitialize}; ================================================ FILE: dist/esm/callback.js ================================================ const o=(o,t,i,n)=>{o&&"function"==typeof o&&(void 0===n?void 0===i?o(t):o(t,i):o(t,i,n))};export{o as safeCallback}; ================================================ FILE: dist/esm/cancelOnExit.js ================================================ import{removeEventListeners as o}from"./event.js";import{resetSourcesImg as r}from"./reset.js";import{restoreImg as t}from"./restore.js";import{safeCallback as m}from"./callback.js";import{removeClass as s}from"./class.js";import{updateLoadingCount as a}from"./counters.js";import{hasStatusLoading as c,resetStatus as e}from"./data.js";const i=(i,l,p,f)=>{p.cancel_on_exit&&c(i)&&"IMG"===i.tagName&&(o(i),r(i),t(i),s(i,p.class_loading),a(f,-1),e(i),m(p.callback_cancel,i,l,f))};export{i as cancelLoading}; ================================================ FILE: dist/esm/class.js ================================================ import{runningOnBrowser as s}from"./environment.js";const o=(o,t)=>{s&&""!==t&&o.classList.add(t)},t=(o,t)=>{s&&""!==t&&o.classList.remove(t)};export{o as addClass,t as removeClass}; ================================================ FILE: dist/esm/constants.js ================================================ const s="src",t="srcset",r="sizes",e="poster",a="llOriginalAttrs",c="data";export{c as DATA,a as ORIGINALS,e as POSTER,r as SIZES,s as SRC,t as SRCSET}; ================================================ FILE: dist/esm/counters.js ================================================ const o=(o,t)=>{o&&(o.loadingCount+=t)},t=o=>{o&&(o.toLoadCount-=1)},n=(o,t)=>{o&&(o.toLoadCount=t)},a=o=>o.loadingCount>0,d=o=>o.toLoadCount>0;export{t as decreaseToLoadCount,d as haveElementsToLoad,a as isSomethingLoading,n as setToLoadCount,o as updateLoadingCount}; ================================================ FILE: dist/esm/data.js ================================================ import{statusNative as t,statusLoading as e,statusError as l,statusLoaded as u,statusApplied as n}from"./elementStatus.js";const r="data-",s="ll-status",o=(t,e)=>t.getAttribute(r+e),i=(t,e,l)=>{const u=r+e;null!==l?t.setAttribute(u,l):t.removeAttribute(u)},a=t=>o(t,s),m=(t,e)=>i(t,s,e),b=t=>m(t,null),A=t=>null===a(t),c=t=>a(t)===e,d=t=>a(t)===l,f=e=>a(e)===t,p=[e,u,n,l],x=t=>p.indexOf(a(t))>=0;export{o as getData,a as getStatus,x as hadStartedLoading,A as hasEmptyStatus,d as hasStatusError,c as hasStatusLoading,f as hasStatusNative,b as resetStatus,i as setData,m as setStatus}; ================================================ FILE: dist/esm/defaults.js ================================================ import{isBot as l,runningOnBrowser as e}from"./environment.js";const a={elements_selector:".lazy",container:l||e?document:null,threshold:300,thresholds:null,data_src:"src",data_srcset:"srcset",data_sizes:"sizes",data_bg:"bg",data_bg_hidpi:"bg-hidpi",data_bg_multi:"bg-multi",data_bg_multi_hidpi:"bg-multi-hidpi",data_bg_set:"bg-set",data_poster:"poster",class_applied:"applied",class_loading:"loading",class_loaded:"loaded",class_error:"error",class_entered:"entered",class_exited:"exited",unobserve_completed:!0,unobserve_entered:!1,cancel_on_exit:!0,callback_enter:null,callback_exit:null,callback_applied:null,callback_loading:null,callback_loaded:null,callback_error:null,callback_finish:null,callback_cancel:null,use_native:!1,restore_on_error:!1},t=l=>Object.assign({},a,l);export{t as getExtendedSettings}; ================================================ FILE: dist/esm/dom.js ================================================ import{hasEmptyStatus as e,hasStatusError as r}from"./data.js";const t=e=>Array.prototype.slice.call(e),l=e=>e.container.querySelectorAll(e.elements_selector),o=r=>t(r).filter(e),c=e=>r(e),a=e=>t(e).filter(c),i=(e,r)=>o(e||l(r));export{o as excludeManagedElements,a as filterErrorElements,i as getElementsToLoad,c as hasError,l as queryElements,t as toArray}; ================================================ FILE: dist/esm/elementStatus.js ================================================ const e="loading",d="loaded",o="applied",r="entered",a="error",n="native";export{o as statusApplied,r as statusEntered,a as statusError,d as statusLoaded,e as statusLoading,n as statusNative}; ================================================ FILE: dist/esm/environment.js ================================================ const e="undefined"!=typeof window,i=e&&!("onscroll"in window)||"undefined"!=typeof navigator&&/(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent),n=e&&window.devicePixelRatio>1;export{i as isBot,n as isHiDpi,e as runningOnBrowser}; ================================================ FILE: dist/esm/event.js ================================================ import{addClass as r,removeClass as o}from"./class.js";import{safeCallback as s}from"./callback.js";import{hasStatusNative as e,setStatus as t}from"./data.js";import{statusLoaded as l,statusError as a}from"./elementStatus.js";import{getTempImage as n,deleteTempImage as i}from"./tempImage.js";import{unobserve as m}from"./unobserve.js";import{isSomethingLoading as c,haveElementsToLoad as d,updateLoadingCount as f,decreaseToLoadCount as p}from"./counters.js";import{restoreOriginalAttrs as E,attrsSrcSrcsetSizes as v}from"./originalAttributes.js";const _=["IMG","IFRAME","VIDEO","OBJECT"],j=r=>_.indexOf(r.tagName)>-1,b=(r,o)=>{!o||c(o)||d(o)||s(r.callback_finish,o)},L=(r,o,s)=>{r.addEventListener(o,s),r.llEvLisnrs[o]=s},u=(r,o,s)=>{r.removeEventListener(o,s)},g=r=>!!r.llEvLisnrs,I=(r,o,s)=>{g(r)||(r.llEvLisnrs={});const e="VIDEO"===r.tagName?"loadeddata":"load";L(r,e,o),L(r,"error",s)},k=r=>{if(!g(r))return;const o=r.llEvLisnrs;for(let s in o){const e=o[s];u(r,s,e)}delete r.llEvLisnrs},O=(r,s,e)=>{i(r),f(e,-1),p(e),o(r,s.class_loading),s.unobserve_completed&&m(r,e)},x=(o,a,n,i)=>{const m=e(a);O(a,n,i),r(a,n.class_loaded),t(a,l),s(n.callback_loaded,a,i),m||b(n,i)},A=(o,l,n,i)=>{const m=e(l);O(l,n,i),r(l,n.class_error),t(l,a),s(n.callback_error,l,i),n.restore_on_error&&E(l,v),m||b(n,i)},D=(r,o,s)=>{const e=n(r)||r;g(e)||I(e,(t=>{x(0,r,o,s),k(e)}),(t=>{A(0,r,o,s),k(e)}))};export{L as addEventListener,I as addEventListeners,D as addOneShotEventListeners,b as checkFinish,O as doneHandler,A as errorHandler,g as hasEventListeners,j as hasLoadEvent,x as loadHandler,u as removeEventListener,k as removeEventListeners}; ================================================ FILE: dist/esm/forEachSource.js ================================================ const e=e=>{let t=[];for(let r,a=0;r=e.children[a];a+=1)"SOURCE"===r.tagName&&t.push(r);return t},t=(t,r)=>{const a=t.parentNode;a&&"PICTURE"===a.tagName&&e(a).forEach(r)},r=(t,r)=>{e(t).forEach(r)};export{t as forEachPictureSource,r as forEachVideoSource}; ================================================ FILE: dist/esm/intersectionHandlers.js ================================================ import{safeCallback as t}from"./callback.js";import{load as o}from"./load.js";import{hadStartedLoading as s,setStatus as r,hasEmptyStatus as e}from"./data.js";import{cancelLoading as a}from"./cancelOnExit.js";import{unobserveEntered as m}from"./unobserve.js";import{statusEntered as c}from"./elementStatus.js";import{addClass as l,removeClass as i}from"./class.js";const n=(e,a,n,p)=>{const f=s(e);r(e,c),l(e,n.class_entered),i(e,n.class_exited),m(e,n,p),t(n.callback_enter,e,a,p),f||o(e,n,p)},p=(o,s,r,m)=>{e(o)||(l(o,r.class_exited),a(o,s,r,m),t(r.callback_exit,o,s,m))};export{n as onEnter,p as onExit}; ================================================ FILE: dist/esm/intersectionObserver.js ================================================ import{onEnter as r,onExit as e}from"./intersectionHandlers.js";import{shouldUseNative as o}from"./native.js";import{resetObserver as t}from"./unobserve.js";const n=r=>r.isIntersecting||r.intersectionRatio>0,s=(r,e)=>{e.forEach((e=>{r.observe(e)}))},i=(r,e)=>{t(r),s(r,e)},a=(t,s)=>{o(t)||(s._observer=new IntersectionObserver((o=>{((o,t,s)=>{o.forEach((o=>n(o)?r(o.target,o,t,s):e(o.target,o,t,s)))})(o,t,s)}),(r=>({root:r.container===document?null:r.container,rootMargin:r.thresholds||r.threshold+"px"}))(t)))};export{n as isIntersecting,s as observeElements,a as setObserver,i as updateObserver}; ================================================ FILE: dist/esm/lazyload.js ================================================ import{getExtendedSettings as t}from"./defaults.js";import{autoInitialize as o}from"./autoInitialize.js";import{load as s}from"./load.js";import{setObserver as i,updateObserver as e}from"./intersectionObserver.js";import{runningOnBrowser as r,isBot as n}from"./environment.js";import{shouldUseNative as m,loadAllNative as h}from"./native.js";import{setOnlineCheck as l,resetOnlineCheck as a}from"./online.js";import{getElementsToLoad as d,queryElements as f}from"./dom.js";import{resetStatus as p}from"./data.js";import{setToLoadCount as c}from"./counters.js";import{unobserve as u}from"./unobserve.js";import{restore as j}from"./restore.js";import{deleteOriginalAttrs as _}from"./originalAttributes.js";const g=function(o,s){const e=t(o);this._settings=e,this.loadingCount=0,i(e,this),l(e,this),this.update(s)};g.prototype={update:function(t){const o=this._settings,s=d(t,o);c(this,s.length),n?this.loadAll(s):m(o)?h(s,o,this):e(this._observer,s)},destroy:function(){this._observer&&this._observer.disconnect(),a(this),f(this._settings).forEach((t=>{_(t)})),delete this._observer,delete this._settings,delete this._onlineHandler,delete this.loadingCount,delete this.toLoadCount},loadAll:function(t){const o=this._settings;d(t,o).forEach((t=>{u(t,this),s(t,o,this)}))},restoreAll:function(){const t=this._settings;f(t).forEach((o=>{j(o,t)}))}},g.load=(o,i)=>{const e=t(i);s(o,e)},g.resetStatus=t=>{p(t)},r&&o(g,window.lazyLoadOptions);export{g as default}; ================================================ FILE: dist/esm/load.js ================================================ import{setBackground as t,setMultiBackground as o,setImgsetBackground as r,setSources as m,setSourcesNative as e}from"./set.js";import{setStatus as i}from"./data.js";import{hasLoadEvent as s,addOneShotEventListeners as p}from"./event.js";import{statusNative as a}from"./elementStatus.js";import{addTempImage as f}from"./tempImage.js";import{saveOriginalBackgroundStyle as j}from"./originalAttributes.js";const n=(e,i,a)=>{s(e)?((t,o,r)=>{p(t,o,r),m(t,o,r)})(e,i,a):((m,e,i)=>{f(m),p(m,e,i),j(m),t(m,e,i),o(m,e,i),r(m,e,i)})(e,i,a)},l=(t,o,r)=>{t.setAttribute("loading","lazy"),p(t,o,r),e(t,o),i(t,a)};export{n as load,l as loadNative}; ================================================ FILE: dist/esm/native.js ================================================ import{loadNative as o}from"./load.js";import{setToLoadCount as e}from"./counters.js";const t=["IMG","IFRAME","VIDEO"],r=o=>o.use_native&&"loading"in HTMLImageElement.prototype,a=(r,a,m)=>{r.forEach((e=>{-1!==t.indexOf(e.tagName)&&o(e,a,m)})),e(m,0)};export{a as loadAllNative,r as shouldUseNative}; ================================================ FILE: dist/esm/online.js ================================================ import{runningOnBrowser as n}from"./environment.js";import{resetStatus as o}from"./data.js";import{removeClass as e}from"./class.js";import{filterErrorElements as r,queryElements as i}from"./dom.js";const t=(n,t)=>{r(i(n)).forEach((r=>{e(r,n.class_error),o(r)})),t.update()},m=(o,e)=>{n&&(e._onlineHandler=()=>{t(o,e)},window.addEventListener("online",e._onlineHandler))},s=o=>{n&&window.removeEventListener("online",o._onlineHandler)};export{s as resetOnlineCheck,t as retryLazyLoad,m as setOnlineCheck}; ================================================ FILE: dist/esm/originalAttributes.js ================================================ import{ORIGINALS as t,SRC as e,POSTER as r,SRCSET as o,SIZES as n,DATA as a}from"./constants.js";const c=[e],s=[e,r],u=[e,o,n],g=[a],b=e=>!!e[t],i=e=>e[t],m=e=>delete e[t],f=(e,r)=>{if(b(e))return;const o={};r.forEach((t=>{o[t]=e.getAttribute(t)})),e[t]=o},d=e=>{b(e)||(e[t]={backgroundImage:e.style.backgroundImage})},k=(t,e)=>{if(!b(t))return;const r=i(t);e.forEach((e=>{((t,e,r)=>{r?t.setAttribute(e,r):t.removeAttribute(e)})(t,e,r[e])}))},I=t=>{if(!b(t))return;const e=i(t);t.style.backgroundImage=e.backgroundImage};export{g as attrsData,c as attrsSrc,s as attrsSrcPoster,u as attrsSrcSrcsetSizes,m as deleteOriginalAttrs,i as getOriginalAttrs,b as hasOriginalAttrs,k as restoreOriginalAttrs,I as restoreOriginalBgImage,d as saveOriginalBackgroundStyle,f as setOriginalsObject}; ================================================ FILE: dist/esm/reset.js ================================================ import{SRC as t,SRCSET as r,SIZES as o}from"./constants.js";import{forEachPictureSource as e}from"./forEachSource.js";const m=e=>{e.removeAttribute(t),e.removeAttribute(r),e.removeAttribute(o)},i=t=>{e(t,(t=>{m(t)})),m(t)};export{i as resetSourcesImg}; ================================================ FILE: dist/esm/restore.js ================================================ import{removeClass as s}from"./class.js";import{resetStatus as o,hasEmptyStatus as a,hasStatusNative as r}from"./data.js";import{forEachPictureSource as t,forEachVideoSource as e}from"./forEachSource.js";import{deleteOriginalAttrs as l,restoreOriginalBgImage as c,restoreOriginalAttrs as i,attrsSrcSrcsetSizes as m,attrsSrc as d,attrsSrcPoster as p,attrsData as _}from"./originalAttributes.js";const f=s=>{t(s,(s=>{i(s,m)})),i(s,m)},n=s=>{e(s,(s=>{i(s,d)})),i(s,p),s.load()},j=s=>{i(s,d)},E=s=>{i(s,_)},g={IMG:f,IFRAME:j,VIDEO:n,OBJECT:E},I=(t,e)=>{(s=>{const o=g[s.tagName];o?o(s):c(s)})(t),((o,t)=>{a(o)||r(o)||(s(o,t.class_entered),s(o,t.class_exited),s(o,t.class_applied),s(o,t.class_loading),s(o,t.class_loaded),s(o,t.class_error))})(t,e),o(t),l(t)};export{I as restore,j as restoreIframe,f as restoreImg,E as restoreObject,n as restoreVideo}; ================================================ FILE: dist/esm/set.js ================================================ import{SRC as t,POSTER as a,DATA as s,SIZES as o,SRCSET as r}from"./constants.js";import{getData as e,setStatus as m}from"./data.js";import{statusApplied as i,statusLoading as c}from"./elementStatus.js";import{safeCallback as d}from"./callback.js";import{addClass as l}from"./class.js";import{getTempImage as n}from"./tempImage.js";import{isHiDpi as p}from"./environment.js";import{unobserve as _}from"./unobserve.js";import{updateLoadingCount as g}from"./counters.js";import{forEachPictureSource as b,forEachVideoSource as u}from"./forEachSource.js";import{setOriginalsObject as f,attrsSrcSrcsetSizes as j,attrsSrc as I,attrsSrcPoster as k,attrsData as A}from"./originalAttributes.js";const E=(t,a,s)=>{l(t,a.class_applied),m(t,i),s&&(a.unobserve_completed&&_(t,a),d(a.callback_applied,t,s))},h=(t,a,s)=>{l(t,a.class_loading),m(t,c),s&&(g(s,1),d(a.callback_loading,t,s))},v=(t,a,s)=>{s&&t.setAttribute(a,s)},y=(a,s)=>{v(a,o,e(a,s.data_sizes)),v(a,r,e(a,s.data_srcset)),v(a,t,e(a,s.data_src))},M=(t,a)=>{b(t,(t=>{f(t,j),y(t,a)})),f(t,j),y(t,a)},N=(a,s)=>{f(a,I),v(a,t,e(a,s.data_src))},O=(s,o)=>{u(s,(a=>{f(a,I),v(a,t,e(a,o.data_src))})),f(s,k),v(s,a,e(s,o.data_poster)),v(s,t,e(s,o.data_src)),s.load()},S=(t,a)=>{f(t,A),v(t,s,e(t,a.data_src))},$=(a,s,o)=>{const r=e(a,s.data_bg),m=e(a,s.data_bg_hidpi),i=p&&m?m:r;i&&(a.style.backgroundImage=`url("${i}")`,n(a).setAttribute(t,i),h(a,s,o))},x=(t,a,s)=>{const o=e(t,a.data_bg_multi),r=e(t,a.data_bg_multi_hidpi),m=p&&r?r:o;m&&(t.style.backgroundImage=m,E(t,a,s))},z=(t,a,s)=>{const o=e(t,a.data_bg_set);if(!o)return;let r=o.split("|").map((t=>`image-set(${t})`));t.style.backgroundImage=r.join(),E(t,a,s)},B={IMG:M,IFRAME:N,VIDEO:O,OBJECT:S},C=(t,a)=>{const s=B[t.tagName];s&&s(t,a)},D=(t,a,s)=>{const o=B[t.tagName];o&&(o(t,a),h(t,a,s))};export{E as manageApplied,h as manageLoading,v as setAttributeIfValue,$ as setBackground,y as setImageAttributes,z as setImgsetBackground,x as setMultiBackground,D as setSources,N as setSourcesIframe,M as setSourcesImg,C as setSourcesNative,S as setSourcesObject,O as setSourcesVideo}; ================================================ FILE: dist/esm/tempImage.js ================================================ const e=e=>{e.llTempImage=document.createElement("IMG")},l=e=>{delete e.llTempImage},m=e=>e.llTempImage;export{e as addTempImage,l as deleteTempImage,m as getTempImage}; ================================================ FILE: dist/esm/unobserve.js ================================================ const e=(e,n)=>{if(!n)return;const r=n._observer;r&&r.unobserve(e)},n=e=>{e.disconnect()},r=(n,r,o)=>{r.unobserve_entered&&e(n,o)};export{n as resetObserver,e as unobserve,r as unobserveEntered}; ================================================ FILE: dist/lazyload.iife.js ================================================ var LazyLoad = (function () { 'use strict'; const runningOnBrowser = typeof window !== "undefined"; const isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent); const isHiDpi = runningOnBrowser && window.devicePixelRatio > 1; const defaultSettings = { elements_selector: ".lazy", container: isBot || runningOnBrowser ? document : null, threshold: 300, thresholds: null, data_src: "src", data_srcset: "srcset", data_sizes: "sizes", data_bg: "bg", data_bg_hidpi: "bg-hidpi", data_bg_multi: "bg-multi", data_bg_multi_hidpi: "bg-multi-hidpi", data_bg_set: "bg-set", data_poster: "poster", class_applied: "applied", class_loading: "loading", class_loaded: "loaded", class_error: "error", class_entered: "entered", class_exited: "exited", unobserve_completed: true, unobserve_entered: false, cancel_on_exit: true, callback_enter: null, callback_exit: null, callback_applied: null, callback_loading: null, callback_loaded: null, callback_error: null, callback_finish: null, callback_cancel: null, use_native: false, restore_on_error: false }; const getExtendedSettings = customSettings => { return Object.assign({}, defaultSettings, customSettings); }; /* Creates instance and notifies it through the window element */ const createInstance = function (classObj, options) { let event; const eventString = "LazyLoad::Initialized"; const instance = new classObj(options); try { // Works in modern browsers event = new CustomEvent(eventString, { detail: { instance } }); } catch (err) { // Works in Internet Explorer (all versions) event = document.createEvent("CustomEvent"); event.initCustomEvent(eventString, false, false, { instance }); } window.dispatchEvent(event); }; /* Auto initialization of one or more instances of LazyLoad, depending on the options passed in (plain object or an array) */ const autoInitialize = (classObj, options) => { if (!options) { return; } if (!options.length) { // Plain object createInstance(classObj, options); } else { // Array of objects for (let i = 0, optionsItem; optionsItem = options[i]; i += 1) { createInstance(classObj, optionsItem); } } }; const SRC = "src"; const SRCSET = "srcset"; const SIZES = "sizes"; const POSTER = "poster"; const ORIGINALS = "llOriginalAttrs"; const DATA = "data"; const statusLoading = "loading"; const statusLoaded = "loaded"; const statusApplied = "applied"; const statusEntered = "entered"; const statusError = "error"; const statusNative = "native"; const dataPrefix = "data-"; const statusDataName = "ll-status"; const getData = (element, attribute) => { return element.getAttribute(dataPrefix + attribute); }; const setData = (element, attribute, value) => { const attrName = dataPrefix + attribute; if (value === null) { element.removeAttribute(attrName); return; } element.setAttribute(attrName, value); }; const getStatus = element => getData(element, statusDataName); const setStatus = (element, status) => setData(element, statusDataName, status); const resetStatus = element => setStatus(element, null); const hasEmptyStatus = element => getStatus(element) === null; const hasStatusLoading = element => getStatus(element) === statusLoading; const hasStatusError = element => getStatus(element) === statusError; const hasStatusNative = element => getStatus(element) === statusNative; const statusesAfterLoading = [statusLoading, statusLoaded, statusApplied, statusError]; const hadStartedLoading = element => statusesAfterLoading.indexOf(getStatus(element)) >= 0; const safeCallback = (callback, arg1, arg2, arg3) => { if (!callback || typeof callback !== 'function') { return; } if (arg3 !== undefined) { callback(arg1, arg2, arg3); return; } if (arg2 !== undefined) { callback(arg1, arg2); return; } callback(arg1); }; const addClass = (element, className) => { if (!runningOnBrowser) { return; } if (className === "") { return; } element.classList.add(className); }; const removeClass = (element, className) => { if (!runningOnBrowser) { return; } if (className === "") { return; } element.classList.remove(className); }; const addTempImage = element => { element.llTempImage = document.createElement("IMG"); }; const deleteTempImage = element => { delete element.llTempImage; }; const getTempImage = element => element.llTempImage; const unobserve = (element, instance) => { if (!instance) return; const observer = instance._observer; if (!observer) return; observer.unobserve(element); }; const resetObserver = observer => { observer.disconnect(); }; const unobserveEntered = (element, settings, instance) => { if (settings.unobserve_entered) unobserve(element, instance); }; const updateLoadingCount = (instance, delta) => { if (!instance) return; instance.loadingCount += delta; }; const decreaseToLoadCount = instance => { if (!instance) return; instance.toLoadCount -= 1; }; const setToLoadCount = (instance, value) => { if (!instance) return; instance.toLoadCount = value; }; const isSomethingLoading = instance => instance.loadingCount > 0; const haveElementsToLoad = instance => instance.toLoadCount > 0; const getSourceTags = parentTag => { let sourceTags = []; for (let i = 0, childTag; childTag = parentTag.children[i]; i += 1) { if (childTag.tagName === "SOURCE") { sourceTags.push(childTag); } } return sourceTags; }; const forEachPictureSource = (element, fn) => { const parent = element.parentNode; if (!parent || parent.tagName !== "PICTURE") { return; } let sourceTags = getSourceTags(parent); sourceTags.forEach(fn); }; const forEachVideoSource = (element, fn) => { let sourceTags = getSourceTags(element); sourceTags.forEach(fn); }; const attrsSrc = [SRC]; const attrsSrcPoster = [SRC, POSTER]; const attrsSrcSrcsetSizes = [SRC, SRCSET, SIZES]; const attrsData = [DATA]; const hasOriginalAttrs = element => !!element[ORIGINALS]; const getOriginalAttrs = element => element[ORIGINALS]; const deleteOriginalAttrs = element => delete element[ORIGINALS]; // ## SAVE ## const setOriginalsObject = (element, attributes) => { if (hasOriginalAttrs(element)) { return; } const originals = {}; attributes.forEach(attribute => { originals[attribute] = element.getAttribute(attribute); }); element[ORIGINALS] = originals; }; const saveOriginalBackgroundStyle = element => { if (hasOriginalAttrs(element)) { return; } element[ORIGINALS] = { backgroundImage: element.style.backgroundImage }; }; // ## RESTORE ## const setOrResetAttribute = (element, attrName, value) => { if (!value) { element.removeAttribute(attrName); return; } element.setAttribute(attrName, value); }; const restoreOriginalAttrs = (element, attributes) => { if (!hasOriginalAttrs(element)) { return; } const originals = getOriginalAttrs(element); attributes.forEach(attribute => { setOrResetAttribute(element, attribute, originals[attribute]); }); }; const restoreOriginalBgImage = element => { if (!hasOriginalAttrs(element)) { return; } const originals = getOriginalAttrs(element); element.style.backgroundImage = originals.backgroundImage; }; const manageApplied = (element, settings, instance) => { addClass(element, settings.class_applied); setStatus(element, statusApplied); // Instance is not provided when loading is called from static class if (!instance) return; if (settings.unobserve_completed) { // Unobserve now because we can't do it on load unobserve(element, settings); } safeCallback(settings.callback_applied, element, instance); }; const manageLoading = (element, settings, instance) => { addClass(element, settings.class_loading); setStatus(element, statusLoading); // Instance is not provided when loading is called from static class if (!instance) return; updateLoadingCount(instance, +1); safeCallback(settings.callback_loading, element, instance); }; const setAttributeIfValue = (element, attrName, value) => { if (!value) { return; } element.setAttribute(attrName, value); }; const setImageAttributes = (element, settings) => { setAttributeIfValue(element, SIZES, getData(element, settings.data_sizes)); setAttributeIfValue(element, SRCSET, getData(element, settings.data_srcset)); setAttributeIfValue(element, SRC, getData(element, settings.data_src)); }; const setSourcesImg = (imgEl, settings) => { forEachPictureSource(imgEl, sourceTag => { setOriginalsObject(sourceTag, attrsSrcSrcsetSizes); setImageAttributes(sourceTag, settings); }); setOriginalsObject(imgEl, attrsSrcSrcsetSizes); setImageAttributes(imgEl, settings); }; const setSourcesIframe = (iframe, settings) => { setOriginalsObject(iframe, attrsSrc); setAttributeIfValue(iframe, SRC, getData(iframe, settings.data_src)); }; const setSourcesVideo = (videoEl, settings) => { forEachVideoSource(videoEl, sourceEl => { setOriginalsObject(sourceEl, attrsSrc); setAttributeIfValue(sourceEl, SRC, getData(sourceEl, settings.data_src)); }); setOriginalsObject(videoEl, attrsSrcPoster); setAttributeIfValue(videoEl, POSTER, getData(videoEl, settings.data_poster)); setAttributeIfValue(videoEl, SRC, getData(videoEl, settings.data_src)); videoEl.load(); }; const setSourcesObject = (object, settings) => { setOriginalsObject(object, attrsData); setAttributeIfValue(object, DATA, getData(object, settings.data_src)); }; const setBackground = (element, settings, instance) => { const bg1xValue = getData(element, settings.data_bg); const bgHiDpiValue = getData(element, settings.data_bg_hidpi); const bgDataValue = isHiDpi && bgHiDpiValue ? bgHiDpiValue : bg1xValue; if (!bgDataValue) return; element.style.backgroundImage = `url("${bgDataValue}")`; getTempImage(element).setAttribute(SRC, bgDataValue); manageLoading(element, settings, instance); }; // NOTE: THE TEMP IMAGE TRICK CANNOT BE DONE WITH data-multi-bg // BECAUSE INSIDE ITS VALUES MUST BE WRAPPED WITH URL() AND ONE OF THEM // COULD BE A GRADIENT BACKGROUND IMAGE const setMultiBackground = (element, settings, instance) => { const bg1xValue = getData(element, settings.data_bg_multi); const bgHiDpiValue = getData(element, settings.data_bg_multi_hidpi); const bgDataValue = isHiDpi && bgHiDpiValue ? bgHiDpiValue : bg1xValue; if (!bgDataValue) { return; } element.style.backgroundImage = bgDataValue; manageApplied(element, settings, instance); }; const setImgsetBackground = (element, settings, instance) => { const bgImgSetDataValue = getData(element, settings.data_bg_set); if (!bgImgSetDataValue) { return; } const imgSetValues = bgImgSetDataValue.split("|"); let bgImageValues = imgSetValues.map(value => `image-set(${value})`); element.style.backgroundImage = bgImageValues.join(); manageApplied(element, settings, instance); }; const setSourcesFunctions = { IMG: setSourcesImg, IFRAME: setSourcesIframe, VIDEO: setSourcesVideo, OBJECT: setSourcesObject }; const setSourcesNative = (element, settings) => { const setSourcesFunction = setSourcesFunctions[element.tagName]; if (!setSourcesFunction) { return; } setSourcesFunction(element, settings); }; const setSources = (element, settings, instance) => { const setSourcesFunction = setSourcesFunctions[element.tagName]; if (!setSourcesFunction) { return; } setSourcesFunction(element, settings); manageLoading(element, settings, instance); }; const elementsWithLoadEvent = ["IMG", "IFRAME", "VIDEO", "OBJECT"]; const hasLoadEvent = element => elementsWithLoadEvent.indexOf(element.tagName) > -1; const checkFinish = (settings, instance) => { if (instance && !isSomethingLoading(instance) && !haveElementsToLoad(instance)) { safeCallback(settings.callback_finish, instance); } }; const addEventListener = (element, eventName, handler) => { element.addEventListener(eventName, handler); element.llEvLisnrs[eventName] = handler; }; const removeEventListener = (element, eventName, handler) => { element.removeEventListener(eventName, handler); }; const hasEventListeners = element => { return !!element.llEvLisnrs; }; const addEventListeners = (element, loadHandler, errorHandler) => { if (!hasEventListeners(element)) element.llEvLisnrs = {}; const loadEventName = element.tagName === "VIDEO" ? "loadeddata" : "load"; addEventListener(element, loadEventName, loadHandler); addEventListener(element, "error", errorHandler); }; const removeEventListeners = element => { if (!hasEventListeners(element)) { return; } const eventListeners = element.llEvLisnrs; for (let eventName in eventListeners) { const handler = eventListeners[eventName]; removeEventListener(element, eventName, handler); } delete element.llEvLisnrs; }; const doneHandler = (element, settings, instance) => { deleteTempImage(element); updateLoadingCount(instance, -1); decreaseToLoadCount(instance); removeClass(element, settings.class_loading); if (settings.unobserve_completed) { unobserve(element, instance); } }; const loadHandler = (event, element, settings, instance) => { const goingNative = hasStatusNative(element); doneHandler(element, settings, instance); addClass(element, settings.class_loaded); setStatus(element, statusLoaded); safeCallback(settings.callback_loaded, element, instance); if (!goingNative) checkFinish(settings, instance); }; const errorHandler = (event, element, settings, instance) => { const goingNative = hasStatusNative(element); doneHandler(element, settings, instance); addClass(element, settings.class_error); setStatus(element, statusError); safeCallback(settings.callback_error, element, instance); if (settings.restore_on_error) restoreOriginalAttrs(element, attrsSrcSrcsetSizes); if (!goingNative) checkFinish(settings, instance); }; const addOneShotEventListeners = (element, settings, instance) => { const elementToListenTo = getTempImage(element) || element; if (hasEventListeners(elementToListenTo)) { // This happens when loading is retried twice return; } const _loadHandler = event => { loadHandler(event, element, settings, instance); removeEventListeners(elementToListenTo); }; const _errorHandler = event => { errorHandler(event, element, settings, instance); removeEventListeners(elementToListenTo); }; addEventListeners(elementToListenTo, _loadHandler, _errorHandler); }; const loadBackground = (element, settings, instance) => { addTempImage(element); addOneShotEventListeners(element, settings, instance); saveOriginalBackgroundStyle(element); setBackground(element, settings, instance); setMultiBackground(element, settings, instance); setImgsetBackground(element, settings, instance); }; const loadRegular = (element, settings, instance) => { addOneShotEventListeners(element, settings, instance); setSources(element, settings, instance); }; const load = (element, settings, instance) => { if (hasLoadEvent(element)) { loadRegular(element, settings, instance); } else { loadBackground(element, settings, instance); } }; const loadNative = (element, settings, instance) => { element.setAttribute("loading", "lazy"); addOneShotEventListeners(element, settings, instance); setSourcesNative(element, settings); setStatus(element, statusNative); }; const removeImageAttributes = element => { element.removeAttribute(SRC); element.removeAttribute(SRCSET); element.removeAttribute(SIZES); }; const resetSourcesImg = element => { forEachPictureSource(element, sourceTag => { removeImageAttributes(sourceTag); }); removeImageAttributes(element); }; const restoreImg = imgEl => { forEachPictureSource(imgEl, sourceEl => { restoreOriginalAttrs(sourceEl, attrsSrcSrcsetSizes); }); restoreOriginalAttrs(imgEl, attrsSrcSrcsetSizes); }; const restoreVideo = videoEl => { forEachVideoSource(videoEl, sourceEl => { restoreOriginalAttrs(sourceEl, attrsSrc); }); restoreOriginalAttrs(videoEl, attrsSrcPoster); videoEl.load(); }; const restoreIframe = iframeEl => { restoreOriginalAttrs(iframeEl, attrsSrc); }; const restoreObject = objectEl => { restoreOriginalAttrs(objectEl, attrsData); }; const restoreFunctions = { IMG: restoreImg, IFRAME: restoreIframe, VIDEO: restoreVideo, OBJECT: restoreObject }; const restoreAttributes = element => { const restoreFunction = restoreFunctions[element.tagName]; if (!restoreFunction) { restoreOriginalBgImage(element); return; } restoreFunction(element); }; const resetClasses = (element, settings) => { if (hasEmptyStatus(element) || hasStatusNative(element)) { return; } removeClass(element, settings.class_entered); removeClass(element, settings.class_exited); removeClass(element, settings.class_applied); removeClass(element, settings.class_loading); removeClass(element, settings.class_loaded); removeClass(element, settings.class_error); }; const restore = (element, settings) => { restoreAttributes(element); resetClasses(element, settings); resetStatus(element); deleteOriginalAttrs(element); }; const cancelLoading = (element, entry, settings, instance) => { if (!settings.cancel_on_exit) return; if (!hasStatusLoading(element)) return; if (element.tagName !== "IMG") return; //Works only on images removeEventListeners(element); resetSourcesImg(element); restoreImg(element); removeClass(element, settings.class_loading); updateLoadingCount(instance, -1); resetStatus(element); safeCallback(settings.callback_cancel, element, entry, instance); }; const onEnter = (element, entry, settings, instance) => { const dontLoad = hadStartedLoading(element); /* Save status before setting it, to prevent loading it again. Fixes #526. */ setStatus(element, statusEntered); addClass(element, settings.class_entered); removeClass(element, settings.class_exited); unobserveEntered(element, settings, instance); safeCallback(settings.callback_enter, element, entry, instance); if (dontLoad) return; load(element, settings, instance); }; const onExit = (element, entry, settings, instance) => { if (hasEmptyStatus(element)) return; //Ignore the first pass, at landing addClass(element, settings.class_exited); cancelLoading(element, entry, settings, instance); safeCallback(settings.callback_exit, element, entry, instance); }; const tagsWithNativeLazy = ["IMG", "IFRAME", "VIDEO"]; const shouldUseNative = settings => settings.use_native && "loading" in HTMLImageElement.prototype; const loadAllNative = (elements, settings, instance) => { elements.forEach(element => { if (tagsWithNativeLazy.indexOf(element.tagName) === -1) { return; } loadNative(element, settings, instance); }); setToLoadCount(instance, 0); }; const isIntersecting = entry => entry.isIntersecting || entry.intersectionRatio > 0; const getObserverSettings = settings => ({ root: settings.container === document ? null : settings.container, rootMargin: settings.thresholds || settings.threshold + "px" }); const intersectionHandler = (entries, settings, instance) => { entries.forEach(entry => isIntersecting(entry) ? onEnter(entry.target, entry, settings, instance) : onExit(entry.target, entry, settings, instance)); }; const observeElements = (observer, elements) => { elements.forEach(element => { observer.observe(element); }); }; const updateObserver = (observer, elementsToObserve) => { resetObserver(observer); observeElements(observer, elementsToObserve); }; const setObserver = (settings, instance) => { if (shouldUseNative(settings)) { return; } instance._observer = new IntersectionObserver(entries => { intersectionHandler(entries, settings, instance); }, getObserverSettings(settings)); }; const toArray = nodeSet => Array.prototype.slice.call(nodeSet); const queryElements = settings => settings.container.querySelectorAll(settings.elements_selector); const excludeManagedElements = elements => toArray(elements).filter(hasEmptyStatus); const hasError = element => hasStatusError(element); const filterErrorElements = elements => toArray(elements).filter(hasError); const getElementsToLoad = (elements, settings) => excludeManagedElements(elements || queryElements(settings)); const retryLazyLoad = (settings, instance) => { const errorElements = filterErrorElements(queryElements(settings)); errorElements.forEach(element => { removeClass(element, settings.class_error); resetStatus(element); }); instance.update(); }; const setOnlineCheck = (settings, instance) => { if (!runningOnBrowser) { return; } instance._onlineHandler = () => { retryLazyLoad(settings, instance); }; window.addEventListener("online", instance._onlineHandler); }; const resetOnlineCheck = instance => { if (!runningOnBrowser) { return; } window.removeEventListener("online", instance._onlineHandler); }; const LazyLoad = function (customSettings, elements) { const settings = getExtendedSettings(customSettings); this._settings = settings; this.loadingCount = 0; setObserver(settings, this); setOnlineCheck(settings, this); this.update(elements); }; LazyLoad.prototype = { update: function (givenNodeset) { const settings = this._settings; const elementsToLoad = getElementsToLoad(givenNodeset, settings); setToLoadCount(this, elementsToLoad.length); if (isBot) { this.loadAll(elementsToLoad); return; } if (shouldUseNative(settings)) { loadAllNative(elementsToLoad, settings, this); return; } updateObserver(this._observer, elementsToLoad); }, destroy: function () { // Observer if (this._observer) { this._observer.disconnect(); } // Clean handlers resetOnlineCheck(this); // Clean custom attributes on elements queryElements(this._settings).forEach(element => { deleteOriginalAttrs(element); }); // Delete all internal props delete this._observer; delete this._settings; delete this._onlineHandler; delete this.loadingCount; delete this.toLoadCount; }, loadAll: function (elements) { const settings = this._settings; const elementsToLoad = getElementsToLoad(elements, settings); elementsToLoad.forEach(element => { unobserve(element, this); load(element, settings, this); }); }, restoreAll: function () { const settings = this._settings; queryElements(settings).forEach(element => { restore(element, settings); }); } }; LazyLoad.load = (element, customSettings) => { const settings = getExtendedSettings(customSettings); load(element, settings); }; LazyLoad.resetStatus = element => { resetStatus(element); }; // Automatic instances creation if required (useful for async script loading) if (runningOnBrowser) { autoInitialize(LazyLoad, window.lazyLoadOptions); } return LazyLoad; })(); ================================================ FILE: dist/lazyload.js ================================================ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.LazyLoad = factory()); })(this, (function () { 'use strict'; const runningOnBrowser = typeof window !== "undefined"; const isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent); const isHiDpi = runningOnBrowser && window.devicePixelRatio > 1; const defaultSettings = { elements_selector: ".lazy", container: isBot || runningOnBrowser ? document : null, threshold: 300, thresholds: null, data_src: "src", data_srcset: "srcset", data_sizes: "sizes", data_bg: "bg", data_bg_hidpi: "bg-hidpi", data_bg_multi: "bg-multi", data_bg_multi_hidpi: "bg-multi-hidpi", data_bg_set: "bg-set", data_poster: "poster", class_applied: "applied", class_loading: "loading", class_loaded: "loaded", class_error: "error", class_entered: "entered", class_exited: "exited", unobserve_completed: true, unobserve_entered: false, cancel_on_exit: true, callback_enter: null, callback_exit: null, callback_applied: null, callback_loading: null, callback_loaded: null, callback_error: null, callback_finish: null, callback_cancel: null, use_native: false, restore_on_error: false }; const getExtendedSettings = customSettings => { return Object.assign({}, defaultSettings, customSettings); }; /* Creates instance and notifies it through the window element */ const createInstance = function (classObj, options) { let event; const eventString = "LazyLoad::Initialized"; const instance = new classObj(options); try { // Works in modern browsers event = new CustomEvent(eventString, { detail: { instance } }); } catch (err) { // Works in Internet Explorer (all versions) event = document.createEvent("CustomEvent"); event.initCustomEvent(eventString, false, false, { instance }); } window.dispatchEvent(event); }; /* Auto initialization of one or more instances of LazyLoad, depending on the options passed in (plain object or an array) */ const autoInitialize = (classObj, options) => { if (!options) { return; } if (!options.length) { // Plain object createInstance(classObj, options); } else { // Array of objects for (let i = 0, optionsItem; optionsItem = options[i]; i += 1) { createInstance(classObj, optionsItem); } } }; const SRC = "src"; const SRCSET = "srcset"; const SIZES = "sizes"; const POSTER = "poster"; const ORIGINALS = "llOriginalAttrs"; const DATA = "data"; const statusLoading = "loading"; const statusLoaded = "loaded"; const statusApplied = "applied"; const statusEntered = "entered"; const statusError = "error"; const statusNative = "native"; const dataPrefix = "data-"; const statusDataName = "ll-status"; const getData = (element, attribute) => { return element.getAttribute(dataPrefix + attribute); }; const setData = (element, attribute, value) => { const attrName = dataPrefix + attribute; if (value === null) { element.removeAttribute(attrName); return; } element.setAttribute(attrName, value); }; const getStatus = element => getData(element, statusDataName); const setStatus = (element, status) => setData(element, statusDataName, status); const resetStatus = element => setStatus(element, null); const hasEmptyStatus = element => getStatus(element) === null; const hasStatusLoading = element => getStatus(element) === statusLoading; const hasStatusError = element => getStatus(element) === statusError; const hasStatusNative = element => getStatus(element) === statusNative; const statusesAfterLoading = [statusLoading, statusLoaded, statusApplied, statusError]; const hadStartedLoading = element => statusesAfterLoading.indexOf(getStatus(element)) >= 0; const safeCallback = (callback, arg1, arg2, arg3) => { if (!callback || typeof callback !== 'function') { return; } if (arg3 !== undefined) { callback(arg1, arg2, arg3); return; } if (arg2 !== undefined) { callback(arg1, arg2); return; } callback(arg1); }; const addClass = (element, className) => { if (!runningOnBrowser) { return; } if (className === "") { return; } element.classList.add(className); }; const removeClass = (element, className) => { if (!runningOnBrowser) { return; } if (className === "") { return; } element.classList.remove(className); }; const addTempImage = element => { element.llTempImage = document.createElement("IMG"); }; const deleteTempImage = element => { delete element.llTempImage; }; const getTempImage = element => element.llTempImage; const unobserve = (element, instance) => { if (!instance) return; const observer = instance._observer; if (!observer) return; observer.unobserve(element); }; const resetObserver = observer => { observer.disconnect(); }; const unobserveEntered = (element, settings, instance) => { if (settings.unobserve_entered) unobserve(element, instance); }; const updateLoadingCount = (instance, delta) => { if (!instance) return; instance.loadingCount += delta; }; const decreaseToLoadCount = instance => { if (!instance) return; instance.toLoadCount -= 1; }; const setToLoadCount = (instance, value) => { if (!instance) return; instance.toLoadCount = value; }; const isSomethingLoading = instance => instance.loadingCount > 0; const haveElementsToLoad = instance => instance.toLoadCount > 0; const getSourceTags = parentTag => { let sourceTags = []; for (let i = 0, childTag; childTag = parentTag.children[i]; i += 1) { if (childTag.tagName === "SOURCE") { sourceTags.push(childTag); } } return sourceTags; }; const forEachPictureSource = (element, fn) => { const parent = element.parentNode; if (!parent || parent.tagName !== "PICTURE") { return; } let sourceTags = getSourceTags(parent); sourceTags.forEach(fn); }; const forEachVideoSource = (element, fn) => { let sourceTags = getSourceTags(element); sourceTags.forEach(fn); }; const attrsSrc = [SRC]; const attrsSrcPoster = [SRC, POSTER]; const attrsSrcSrcsetSizes = [SRC, SRCSET, SIZES]; const attrsData = [DATA]; const hasOriginalAttrs = element => !!element[ORIGINALS]; const getOriginalAttrs = element => element[ORIGINALS]; const deleteOriginalAttrs = element => delete element[ORIGINALS]; // ## SAVE ## const setOriginalsObject = (element, attributes) => { if (hasOriginalAttrs(element)) { return; } const originals = {}; attributes.forEach(attribute => { originals[attribute] = element.getAttribute(attribute); }); element[ORIGINALS] = originals; }; const saveOriginalBackgroundStyle = element => { if (hasOriginalAttrs(element)) { return; } element[ORIGINALS] = { backgroundImage: element.style.backgroundImage }; }; // ## RESTORE ## const setOrResetAttribute = (element, attrName, value) => { if (!value) { element.removeAttribute(attrName); return; } element.setAttribute(attrName, value); }; const restoreOriginalAttrs = (element, attributes) => { if (!hasOriginalAttrs(element)) { return; } const originals = getOriginalAttrs(element); attributes.forEach(attribute => { setOrResetAttribute(element, attribute, originals[attribute]); }); }; const restoreOriginalBgImage = element => { if (!hasOriginalAttrs(element)) { return; } const originals = getOriginalAttrs(element); element.style.backgroundImage = originals.backgroundImage; }; const manageApplied = (element, settings, instance) => { addClass(element, settings.class_applied); setStatus(element, statusApplied); // Instance is not provided when loading is called from static class if (!instance) return; if (settings.unobserve_completed) { // Unobserve now because we can't do it on load unobserve(element, settings); } safeCallback(settings.callback_applied, element, instance); }; const manageLoading = (element, settings, instance) => { addClass(element, settings.class_loading); setStatus(element, statusLoading); // Instance is not provided when loading is called from static class if (!instance) return; updateLoadingCount(instance, +1); safeCallback(settings.callback_loading, element, instance); }; const setAttributeIfValue = (element, attrName, value) => { if (!value) { return; } element.setAttribute(attrName, value); }; const setImageAttributes = (element, settings) => { setAttributeIfValue(element, SIZES, getData(element, settings.data_sizes)); setAttributeIfValue(element, SRCSET, getData(element, settings.data_srcset)); setAttributeIfValue(element, SRC, getData(element, settings.data_src)); }; const setSourcesImg = (imgEl, settings) => { forEachPictureSource(imgEl, sourceTag => { setOriginalsObject(sourceTag, attrsSrcSrcsetSizes); setImageAttributes(sourceTag, settings); }); setOriginalsObject(imgEl, attrsSrcSrcsetSizes); setImageAttributes(imgEl, settings); }; const setSourcesIframe = (iframe, settings) => { setOriginalsObject(iframe, attrsSrc); setAttributeIfValue(iframe, SRC, getData(iframe, settings.data_src)); }; const setSourcesVideo = (videoEl, settings) => { forEachVideoSource(videoEl, sourceEl => { setOriginalsObject(sourceEl, attrsSrc); setAttributeIfValue(sourceEl, SRC, getData(sourceEl, settings.data_src)); }); setOriginalsObject(videoEl, attrsSrcPoster); setAttributeIfValue(videoEl, POSTER, getData(videoEl, settings.data_poster)); setAttributeIfValue(videoEl, SRC, getData(videoEl, settings.data_src)); videoEl.load(); }; const setSourcesObject = (object, settings) => { setOriginalsObject(object, attrsData); setAttributeIfValue(object, DATA, getData(object, settings.data_src)); }; const setBackground = (element, settings, instance) => { const bg1xValue = getData(element, settings.data_bg); const bgHiDpiValue = getData(element, settings.data_bg_hidpi); const bgDataValue = isHiDpi && bgHiDpiValue ? bgHiDpiValue : bg1xValue; if (!bgDataValue) return; element.style.backgroundImage = `url("${bgDataValue}")`; getTempImage(element).setAttribute(SRC, bgDataValue); manageLoading(element, settings, instance); }; // NOTE: THE TEMP IMAGE TRICK CANNOT BE DONE WITH data-multi-bg // BECAUSE INSIDE ITS VALUES MUST BE WRAPPED WITH URL() AND ONE OF THEM // COULD BE A GRADIENT BACKGROUND IMAGE const setMultiBackground = (element, settings, instance) => { const bg1xValue = getData(element, settings.data_bg_multi); const bgHiDpiValue = getData(element, settings.data_bg_multi_hidpi); const bgDataValue = isHiDpi && bgHiDpiValue ? bgHiDpiValue : bg1xValue; if (!bgDataValue) { return; } element.style.backgroundImage = bgDataValue; manageApplied(element, settings, instance); }; const setImgsetBackground = (element, settings, instance) => { const bgImgSetDataValue = getData(element, settings.data_bg_set); if (!bgImgSetDataValue) { return; } const imgSetValues = bgImgSetDataValue.split("|"); let bgImageValues = imgSetValues.map(value => `image-set(${value})`); element.style.backgroundImage = bgImageValues.join(); manageApplied(element, settings, instance); }; const setSourcesFunctions = { IMG: setSourcesImg, IFRAME: setSourcesIframe, VIDEO: setSourcesVideo, OBJECT: setSourcesObject }; const setSourcesNative = (element, settings) => { const setSourcesFunction = setSourcesFunctions[element.tagName]; if (!setSourcesFunction) { return; } setSourcesFunction(element, settings); }; const setSources = (element, settings, instance) => { const setSourcesFunction = setSourcesFunctions[element.tagName]; if (!setSourcesFunction) { return; } setSourcesFunction(element, settings); manageLoading(element, settings, instance); }; const elementsWithLoadEvent = ["IMG", "IFRAME", "VIDEO", "OBJECT"]; const hasLoadEvent = element => elementsWithLoadEvent.indexOf(element.tagName) > -1; const checkFinish = (settings, instance) => { if (instance && !isSomethingLoading(instance) && !haveElementsToLoad(instance)) { safeCallback(settings.callback_finish, instance); } }; const addEventListener = (element, eventName, handler) => { element.addEventListener(eventName, handler); element.llEvLisnrs[eventName] = handler; }; const removeEventListener = (element, eventName, handler) => { element.removeEventListener(eventName, handler); }; const hasEventListeners = element => { return !!element.llEvLisnrs; }; const addEventListeners = (element, loadHandler, errorHandler) => { if (!hasEventListeners(element)) element.llEvLisnrs = {}; const loadEventName = element.tagName === "VIDEO" ? "loadeddata" : "load"; addEventListener(element, loadEventName, loadHandler); addEventListener(element, "error", errorHandler); }; const removeEventListeners = element => { if (!hasEventListeners(element)) { return; } const eventListeners = element.llEvLisnrs; for (let eventName in eventListeners) { const handler = eventListeners[eventName]; removeEventListener(element, eventName, handler); } delete element.llEvLisnrs; }; const doneHandler = (element, settings, instance) => { deleteTempImage(element); updateLoadingCount(instance, -1); decreaseToLoadCount(instance); removeClass(element, settings.class_loading); if (settings.unobserve_completed) { unobserve(element, instance); } }; const loadHandler = (event, element, settings, instance) => { const goingNative = hasStatusNative(element); doneHandler(element, settings, instance); addClass(element, settings.class_loaded); setStatus(element, statusLoaded); safeCallback(settings.callback_loaded, element, instance); if (!goingNative) checkFinish(settings, instance); }; const errorHandler = (event, element, settings, instance) => { const goingNative = hasStatusNative(element); doneHandler(element, settings, instance); addClass(element, settings.class_error); setStatus(element, statusError); safeCallback(settings.callback_error, element, instance); if (settings.restore_on_error) restoreOriginalAttrs(element, attrsSrcSrcsetSizes); if (!goingNative) checkFinish(settings, instance); }; const addOneShotEventListeners = (element, settings, instance) => { const elementToListenTo = getTempImage(element) || element; if (hasEventListeners(elementToListenTo)) { // This happens when loading is retried twice return; } const _loadHandler = event => { loadHandler(event, element, settings, instance); removeEventListeners(elementToListenTo); }; const _errorHandler = event => { errorHandler(event, element, settings, instance); removeEventListeners(elementToListenTo); }; addEventListeners(elementToListenTo, _loadHandler, _errorHandler); }; const loadBackground = (element, settings, instance) => { addTempImage(element); addOneShotEventListeners(element, settings, instance); saveOriginalBackgroundStyle(element); setBackground(element, settings, instance); setMultiBackground(element, settings, instance); setImgsetBackground(element, settings, instance); }; const loadRegular = (element, settings, instance) => { addOneShotEventListeners(element, settings, instance); setSources(element, settings, instance); }; const load = (element, settings, instance) => { if (hasLoadEvent(element)) { loadRegular(element, settings, instance); } else { loadBackground(element, settings, instance); } }; const loadNative = (element, settings, instance) => { element.setAttribute("loading", "lazy"); addOneShotEventListeners(element, settings, instance); setSourcesNative(element, settings); setStatus(element, statusNative); }; const removeImageAttributes = element => { element.removeAttribute(SRC); element.removeAttribute(SRCSET); element.removeAttribute(SIZES); }; const resetSourcesImg = element => { forEachPictureSource(element, sourceTag => { removeImageAttributes(sourceTag); }); removeImageAttributes(element); }; const restoreImg = imgEl => { forEachPictureSource(imgEl, sourceEl => { restoreOriginalAttrs(sourceEl, attrsSrcSrcsetSizes); }); restoreOriginalAttrs(imgEl, attrsSrcSrcsetSizes); }; const restoreVideo = videoEl => { forEachVideoSource(videoEl, sourceEl => { restoreOriginalAttrs(sourceEl, attrsSrc); }); restoreOriginalAttrs(videoEl, attrsSrcPoster); videoEl.load(); }; const restoreIframe = iframeEl => { restoreOriginalAttrs(iframeEl, attrsSrc); }; const restoreObject = objectEl => { restoreOriginalAttrs(objectEl, attrsData); }; const restoreFunctions = { IMG: restoreImg, IFRAME: restoreIframe, VIDEO: restoreVideo, OBJECT: restoreObject }; const restoreAttributes = element => { const restoreFunction = restoreFunctions[element.tagName]; if (!restoreFunction) { restoreOriginalBgImage(element); return; } restoreFunction(element); }; const resetClasses = (element, settings) => { if (hasEmptyStatus(element) || hasStatusNative(element)) { return; } removeClass(element, settings.class_entered); removeClass(element, settings.class_exited); removeClass(element, settings.class_applied); removeClass(element, settings.class_loading); removeClass(element, settings.class_loaded); removeClass(element, settings.class_error); }; const restore = (element, settings) => { restoreAttributes(element); resetClasses(element, settings); resetStatus(element); deleteOriginalAttrs(element); }; const cancelLoading = (element, entry, settings, instance) => { if (!settings.cancel_on_exit) return; if (!hasStatusLoading(element)) return; if (element.tagName !== "IMG") return; //Works only on images removeEventListeners(element); resetSourcesImg(element); restoreImg(element); removeClass(element, settings.class_loading); updateLoadingCount(instance, -1); resetStatus(element); safeCallback(settings.callback_cancel, element, entry, instance); }; const onEnter = (element, entry, settings, instance) => { const dontLoad = hadStartedLoading(element); /* Save status before setting it, to prevent loading it again. Fixes #526. */ setStatus(element, statusEntered); addClass(element, settings.class_entered); removeClass(element, settings.class_exited); unobserveEntered(element, settings, instance); safeCallback(settings.callback_enter, element, entry, instance); if (dontLoad) return; load(element, settings, instance); }; const onExit = (element, entry, settings, instance) => { if (hasEmptyStatus(element)) return; //Ignore the first pass, at landing addClass(element, settings.class_exited); cancelLoading(element, entry, settings, instance); safeCallback(settings.callback_exit, element, entry, instance); }; const tagsWithNativeLazy = ["IMG", "IFRAME", "VIDEO"]; const shouldUseNative = settings => settings.use_native && "loading" in HTMLImageElement.prototype; const loadAllNative = (elements, settings, instance) => { elements.forEach(element => { if (tagsWithNativeLazy.indexOf(element.tagName) === -1) { return; } loadNative(element, settings, instance); }); setToLoadCount(instance, 0); }; const isIntersecting = entry => entry.isIntersecting || entry.intersectionRatio > 0; const getObserverSettings = settings => ({ root: settings.container === document ? null : settings.container, rootMargin: settings.thresholds || settings.threshold + "px" }); const intersectionHandler = (entries, settings, instance) => { entries.forEach(entry => isIntersecting(entry) ? onEnter(entry.target, entry, settings, instance) : onExit(entry.target, entry, settings, instance)); }; const observeElements = (observer, elements) => { elements.forEach(element => { observer.observe(element); }); }; const updateObserver = (observer, elementsToObserve) => { resetObserver(observer); observeElements(observer, elementsToObserve); }; const setObserver = (settings, instance) => { if (shouldUseNative(settings)) { return; } instance._observer = new IntersectionObserver(entries => { intersectionHandler(entries, settings, instance); }, getObserverSettings(settings)); }; const toArray = nodeSet => Array.prototype.slice.call(nodeSet); const queryElements = settings => settings.container.querySelectorAll(settings.elements_selector); const excludeManagedElements = elements => toArray(elements).filter(hasEmptyStatus); const hasError = element => hasStatusError(element); const filterErrorElements = elements => toArray(elements).filter(hasError); const getElementsToLoad = (elements, settings) => excludeManagedElements(elements || queryElements(settings)); const retryLazyLoad = (settings, instance) => { const errorElements = filterErrorElements(queryElements(settings)); errorElements.forEach(element => { removeClass(element, settings.class_error); resetStatus(element); }); instance.update(); }; const setOnlineCheck = (settings, instance) => { if (!runningOnBrowser) { return; } instance._onlineHandler = () => { retryLazyLoad(settings, instance); }; window.addEventListener("online", instance._onlineHandler); }; const resetOnlineCheck = instance => { if (!runningOnBrowser) { return; } window.removeEventListener("online", instance._onlineHandler); }; const LazyLoad = function (customSettings, elements) { const settings = getExtendedSettings(customSettings); this._settings = settings; this.loadingCount = 0; setObserver(settings, this); setOnlineCheck(settings, this); this.update(elements); }; LazyLoad.prototype = { update: function (givenNodeset) { const settings = this._settings; const elementsToLoad = getElementsToLoad(givenNodeset, settings); setToLoadCount(this, elementsToLoad.length); if (isBot) { this.loadAll(elementsToLoad); return; } if (shouldUseNative(settings)) { loadAllNative(elementsToLoad, settings, this); return; } updateObserver(this._observer, elementsToLoad); }, destroy: function () { // Observer if (this._observer) { this._observer.disconnect(); } // Clean handlers resetOnlineCheck(this); // Clean custom attributes on elements queryElements(this._settings).forEach(element => { deleteOriginalAttrs(element); }); // Delete all internal props delete this._observer; delete this._settings; delete this._onlineHandler; delete this.loadingCount; delete this.toLoadCount; }, loadAll: function (elements) { const settings = this._settings; const elementsToLoad = getElementsToLoad(elements, settings); elementsToLoad.forEach(element => { unobserve(element, this); load(element, settings, this); }); }, restoreAll: function () { const settings = this._settings; queryElements(settings).forEach(element => { restore(element, settings); }); } }; LazyLoad.load = (element, customSettings) => { const settings = getExtendedSettings(customSettings); load(element, settings); }; LazyLoad.resetStatus = element => { resetStatus(element); }; // Automatic instances creation if required (useful for async script loading) if (runningOnBrowser) { autoInitialize(LazyLoad, window.lazyLoadOptions); } return LazyLoad; })); ================================================ FILE: funding.yml ================================================ github: verlok ko_fi: verlok custom: "https://www.paypal.me/verlok" ================================================ FILE: jest.config.cjs ================================================ /** @type {import('jest').Config} */ module.exports = { verbose: true, testMatch: ["/tests/unit/**/*.test.js"], testEnvironment: 'jsdom', collectCoverage: true, collectCoverageFrom: [ 'src/**/*.js', ] }; ================================================ FILE: package.json ================================================ { "name": "vanilla-lazyload", "version": "19.1.3", "description": "LazyLoad is a lightweight (2.4 kB) and flexible script that speeds up your web application by deferring the loading of your below-the-fold images, videos and iframes to when they will enter the viewport. It's written in plain \"vanilla\" JavaScript, it leverages the IntersectionObserver API, it supports responsive images, it optimizes your website for slower connections, and can enable native lazy loading.", "main": "dist/lazyload.min.js", "module": "dist/esm/lazyload.js", "browser": "dist/lazyload.min.js", "typings": "typings/lazyload.d.ts", "sideEffects": false, "devDependencies": { "@babel/core": "^7.24.3", "@babel/preset-env": "^7.24.3", "@jest/globals": "^29.7.0", "@playwright/test": "^1.56.1", "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-terser": "^0.4.4", "@types/node": "^20.12.2", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "http-server": "^14.1.1", "rollup": "^4.22.4" }, "scripts": { "serve": "npx http-server -p 4200", "build": "rollup -c", "dev": "rollup -c --watch", "test": "npm run test:unit && npm run test:e2e", "test:unit": "jest", "watch-test:unit": "jest --watch", "test:e2e": "playwright test", "test-watch:e2e": "npx playwright test --ui" }, "files": [ "dist", "typings", "CHANGELOG.md" ], "repository": { "type": "git", "url": "https://github.com/verlok/vanilla-lazyload" }, "keywords": [ "lazyload", "vanilla", "responsive", "images", "picture", "srcset", "sizes", "native", "SEO", "intersectionObserver", "progressive", "performance", "perfmatters", "async", "no-jquery" ], "author": { "name": "Andrea \"verlok\" Verlicchi", "email": "andrea.verlicchi@gmail.com", "url": "https://www.andreaverlicchi.eu" }, "license": "MIT", "bugs": { "url": "https://github.com/verlok/vanilla-lazyload/issues" }, "homepage": "https://www.andreaverlicchi.eu/vanilla-lazyload", "funding": { "type": "individual", "url": "https://ko-fi.com/verlok" }, "browserslist": [ "defaults" ] } ================================================ FILE: playwright.config.js ================================================ // @ts-check const { defineConfig, devices } = require('@playwright/test'); /** * Read environment variables from file. * https://github.com/motdotla/dotenv */ // require('dotenv').config(); /** * @see https://playwright.dev/docs/test-configuration */ module.exports = defineConfig({ testDir: './tests/e2e', /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ retries: process.env.CI ? 2 : 0, /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ baseURL: 'http://127.0.0.1:4200/', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', }, /* Configure projects for major browsers */ projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, { name: 'firefox', use: { ...devices['Desktop Firefox'] }, }, { name: 'webkit', use: { ...devices['Desktop Safari'] }, }, /* Test against mobile viewports. */ { name: 'Mobile Chrome', use: { ...devices['Pixel 5'] }, }, { name: 'Mobile Safari', use: { ...devices['iPhone 12'] }, }, ], /* Run your local dev server before starting the tests */ webServer: { command: 'npm run serve', url: 'http://127.0.0.1:4200', reuseExistingServer: !process.env.CI, }, }); ================================================ FILE: rollup.config.mjs ================================================ import terser from "@rollup/plugin-terser"; import resolve from "@rollup/plugin-node-resolve"; import babel from "@rollup/plugin-babel"; const terserOptions = { compress: { passes: 2 } }; export default [ { input: "src/lazyload.js", output: [ { file: "dist/lazyload.iife.js", name: "LazyLoad", format: "iife" }, { file: "dist/lazyload.iife.min.js", name: "LazyLoad", format: "iife", plugins: [terser(terserOptions)] }, { file: "dist/lazyload.js", name: "LazyLoad", format: "umd" }, { file: "dist/lazyload.min.js", name: "LazyLoad", format: "umd", plugins: [terser(terserOptions)] } ], plugins: [ resolve(), babel({ babelHelpers: "bundled", exclude: "node_modules/**" }) ] }, { input: "src/lazyload.js", output: [ { dir: "dist/esm", format: "esm", preserveModules: true, plugins: [terser(terserOptions)] } ] } ]; ================================================ FILE: src/autoInitialize.js ================================================ /* Creates instance and notifies it through the window element */ const createInstance = function(classObj, options) { let event; const eventString = "LazyLoad::Initialized"; const instance = new classObj(options); try { // Works in modern browsers event = new CustomEvent(eventString, { detail: { instance } }); } catch (err) { // Works in Internet Explorer (all versions) event = document.createEvent("CustomEvent"); event.initCustomEvent(eventString, false, false, { instance }); } window.dispatchEvent(event); }; /* Auto initialization of one or more instances of LazyLoad, depending on the options passed in (plain object or an array) */ export const autoInitialize = (classObj, options) => { if (!options) { return; } if (!options.length) { // Plain object createInstance(classObj, options); } else { // Array of objects for (let i = 0, optionsItem; (optionsItem = options[i]); i += 1) { createInstance(classObj, optionsItem); } } }; ================================================ FILE: src/callback.js ================================================ export const safeCallback = (callback, arg1, arg2, arg3) => { if (!callback || typeof callback !== 'function') { return; } if (arg3 !== undefined) { callback(arg1, arg2, arg3); return; } if (arg2 !== undefined) { callback(arg1, arg2); return; } callback(arg1); }; ================================================ FILE: src/cancelOnExit.js ================================================ import { removeEventListeners } from "./event"; import { resetSourcesImg } from "./reset"; import { restoreImg } from "./restore"; import { safeCallback } from "./callback"; import { removeClass } from "./class"; import { updateLoadingCount } from "./counters"; import { hasStatusLoading, resetStatus } from "./data"; export const cancelLoading = (element, entry, settings, instance) => { if (!settings.cancel_on_exit) return; if (!hasStatusLoading(element)) return; if (element.tagName !== "IMG") return; //Works only on images removeEventListeners(element); resetSourcesImg(element); restoreImg(element); removeClass(element, settings.class_loading); updateLoadingCount(instance, -1); resetStatus(element); safeCallback(settings.callback_cancel, element, entry, instance); }; ================================================ FILE: src/class.js ================================================ import { runningOnBrowser } from "./environment"; export const addClass = (element, className) => { if (!runningOnBrowser) { return; } if (className === "") { return; } element.classList.add(className); }; export const removeClass = (element, className) => { if (!runningOnBrowser) { return; } if (className === "") { return; } element.classList.remove(className); }; ================================================ FILE: src/constants.js ================================================ export const SRC = "src"; export const SRCSET = "srcset"; export const SIZES = "sizes"; export const POSTER = "poster"; export const ORIGINALS = "llOriginalAttrs"; export const DATA = "data"; ================================================ FILE: src/counters.js ================================================ export const updateLoadingCount = (instance, delta) => { if (!instance) return; instance.loadingCount += delta; }; export const decreaseToLoadCount = (instance) => { if (!instance) return; instance.toLoadCount -= 1; }; export const setToLoadCount = (instance, value) => { if (!instance) return; instance.toLoadCount = value; }; export const isSomethingLoading = (instance) => instance.loadingCount > 0; export const haveElementsToLoad = (instance) => instance.toLoadCount > 0; ================================================ FILE: src/data.js ================================================ import { statusApplied, statusError, statusLoaded, statusLoading, statusNative } from "./elementStatus"; const dataPrefix = "data-"; const statusDataName = "ll-status"; export const getData = (element, attribute) => { return element.getAttribute(dataPrefix + attribute); }; export const setData = (element, attribute, value) => { const attrName = dataPrefix + attribute; if (value === null) { element.removeAttribute(attrName); return; } element.setAttribute(attrName, value); }; export const getStatus = (element) => getData(element, statusDataName); export const setStatus = (element, status) => setData(element, statusDataName, status); export const resetStatus = (element) => setStatus(element, null); export const hasEmptyStatus = (element) => getStatus(element) === null; export const hasStatusLoading = (element) => getStatus(element) === statusLoading; export const hasStatusError = (element) => getStatus(element) === statusError; export const hasStatusNative = (element) => getStatus(element) === statusNative; const statusesAfterLoading = [statusLoading, statusLoaded, statusApplied, statusError]; export const hadStartedLoading = (element) => statusesAfterLoading.indexOf(getStatus(element)) >= 0; ================================================ FILE: src/defaults.js ================================================ import { isBot, runningOnBrowser } from "./environment"; const defaultSettings = { elements_selector: ".lazy", container: isBot || runningOnBrowser ? document : null, threshold: 300, thresholds: null, data_src: "src", data_srcset: "srcset", data_sizes: "sizes", data_bg: "bg", data_bg_hidpi: "bg-hidpi", data_bg_multi: "bg-multi", data_bg_multi_hidpi: "bg-multi-hidpi", data_bg_set: "bg-set", data_poster: "poster", class_applied: "applied", class_loading: "loading", class_loaded: "loaded", class_error: "error", class_entered: "entered", class_exited: "exited", unobserve_completed: true, unobserve_entered: false, cancel_on_exit: true, callback_enter: null, callback_exit: null, callback_applied: null, callback_loading: null, callback_loaded: null, callback_error: null, callback_finish: null, callback_cancel: null, use_native: false, restore_on_error: false }; export const getExtendedSettings = (customSettings) => { return Object.assign({}, defaultSettings, customSettings); }; ================================================ FILE: src/dom.js ================================================ import { hasEmptyStatus, hasStatusError } from "./data"; export const toArray = (nodeSet) => Array.prototype.slice.call(nodeSet); export const queryElements = (settings) => settings.container.querySelectorAll(settings.elements_selector); export const excludeManagedElements = (elements) => toArray(elements).filter(hasEmptyStatus); export const hasError = (element) => hasStatusError(element); export const filterErrorElements = (elements) => toArray(elements).filter(hasError); export const getElementsToLoad = (elements, settings) => excludeManagedElements(elements || queryElements(settings)); ================================================ FILE: src/elementStatus.js ================================================ export const statusLoading = "loading"; export const statusLoaded = "loaded"; export const statusApplied = "applied"; export const statusEntered = "entered"; export const statusError = "error"; export const statusNative = "native"; ================================================ FILE: src/environment.js ================================================ export const runningOnBrowser = typeof window !== "undefined"; export const isBot = (runningOnBrowser && !("onscroll" in window)) || (typeof navigator !== "undefined" && /(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent)); export const isHiDpi = runningOnBrowser && window.devicePixelRatio > 1; ================================================ FILE: src/event.js ================================================ import { addClass, removeClass } from "./class"; import { safeCallback } from "./callback"; import { hasStatusNative, setStatus } from "./data"; import { statusError, statusLoaded } from "./elementStatus"; import { deleteTempImage, getTempImage } from "./tempImage"; import { unobserve } from "./unobserve"; import { decreaseToLoadCount, haveElementsToLoad, isSomethingLoading, updateLoadingCount } from "./counters"; import { attrsSrcSrcsetSizes, restoreOriginalAttrs } from "./originalAttributes"; const elementsWithLoadEvent = ["IMG", "IFRAME", "VIDEO", "OBJECT"]; export const hasLoadEvent = (element) => elementsWithLoadEvent.indexOf(element.tagName) > -1; export const checkFinish = (settings, instance) => { if (instance && !isSomethingLoading(instance) && !haveElementsToLoad(instance)) { safeCallback(settings.callback_finish, instance); } }; export const addEventListener = (element, eventName, handler) => { element.addEventListener(eventName, handler); element.llEvLisnrs[eventName] = handler; }; export const removeEventListener = (element, eventName, handler) => { element.removeEventListener(eventName, handler); }; export const hasEventListeners = (element) => { return !!element.llEvLisnrs; }; export const addEventListeners = (element, loadHandler, errorHandler) => { if (!hasEventListeners(element)) element.llEvLisnrs = {}; const loadEventName = element.tagName === "VIDEO" ? "loadeddata" : "load"; addEventListener(element, loadEventName, loadHandler); addEventListener(element, "error", errorHandler); }; export const removeEventListeners = (element) => { if (!hasEventListeners(element)) { return; } const eventListeners = element.llEvLisnrs; for (let eventName in eventListeners) { const handler = eventListeners[eventName]; removeEventListener(element, eventName, handler); } delete element.llEvLisnrs; }; export const doneHandler = (element, settings, instance) => { deleteTempImage(element); updateLoadingCount(instance, -1); decreaseToLoadCount(instance); removeClass(element, settings.class_loading); if (settings.unobserve_completed) { unobserve(element, instance); } }; export const loadHandler = (event, element, settings, instance) => { const goingNative = hasStatusNative(element); doneHandler(element, settings, instance); addClass(element, settings.class_loaded); setStatus(element, statusLoaded); safeCallback(settings.callback_loaded, element, instance); if (!goingNative) checkFinish(settings, instance); }; export const errorHandler = (event, element, settings, instance) => { const goingNative = hasStatusNative(element); doneHandler(element, settings, instance); addClass(element, settings.class_error); setStatus(element, statusError); safeCallback(settings.callback_error, element, instance); if (settings.restore_on_error) restoreOriginalAttrs(element, attrsSrcSrcsetSizes); if (!goingNative) checkFinish(settings, instance); }; export const addOneShotEventListeners = (element, settings, instance) => { const elementToListenTo = getTempImage(element) || element; if (hasEventListeners(elementToListenTo)) { // This happens when loading is retried twice return; } const _loadHandler = (event) => { loadHandler(event, element, settings, instance); removeEventListeners(elementToListenTo); }; const _errorHandler = (event) => { errorHandler(event, element, settings, instance); removeEventListeners(elementToListenTo); }; addEventListeners(elementToListenTo, _loadHandler, _errorHandler); }; ================================================ FILE: src/forEachSource.js ================================================ const getSourceTags = (parentTag) => { let sourceTags = []; for (let i = 0, childTag; (childTag = parentTag.children[i]); i += 1) { if (childTag.tagName === "SOURCE") { sourceTags.push(childTag); } } return sourceTags; }; export const forEachPictureSource = (element, fn) => { const parent = element.parentNode; if (!parent || parent.tagName !== "PICTURE") { return; } let sourceTags = getSourceTags(parent); sourceTags.forEach(fn); }; export const forEachVideoSource = (element, fn) => { let sourceTags = getSourceTags(element); sourceTags.forEach(fn); }; ================================================ FILE: src/intersectionHandlers.js ================================================ import { safeCallback } from "./callback"; import { load } from "./load"; import { hadStartedLoading, hasEmptyStatus, setStatus } from "./data"; import { cancelLoading } from "./cancelOnExit"; import { unobserveEntered } from "./unobserve"; import { statusEntered } from "./elementStatus"; import { addClass, removeClass } from "./class"; export const onEnter = (element, entry, settings, instance) => { const dontLoad = hadStartedLoading(element); /* Save status before setting it, to prevent loading it again. Fixes #526. */ setStatus(element, statusEntered); addClass(element, settings.class_entered); removeClass(element, settings.class_exited); unobserveEntered(element, settings, instance); safeCallback(settings.callback_enter, element, entry, instance); if (dontLoad) return; load(element, settings, instance); }; export const onExit = (element, entry, settings, instance) => { if (hasEmptyStatus(element)) return; //Ignore the first pass, at landing addClass(element, settings.class_exited); cancelLoading(element, entry, settings, instance); safeCallback(settings.callback_exit, element, entry, instance); }; ================================================ FILE: src/intersectionObserver.js ================================================ import { onEnter, onExit } from "./intersectionHandlers"; import { shouldUseNative } from "./native"; import { resetObserver } from "./unobserve"; export const isIntersecting = (entry) => entry.isIntersecting || entry.intersectionRatio > 0; const getObserverSettings = (settings) => ({ root: settings.container === document ? null : settings.container, rootMargin: settings.thresholds || settings.threshold + "px" }); const intersectionHandler = (entries, settings, instance) => { entries.forEach((entry) => isIntersecting(entry) ? onEnter(entry.target, entry, settings, instance) : onExit(entry.target, entry, settings, instance) ); }; export const observeElements = (observer, elements) => { elements.forEach((element) => { observer.observe(element); }); }; export const updateObserver = (observer, elementsToObserve) => { resetObserver(observer); observeElements(observer, elementsToObserve); }; export const setObserver = (settings, instance) => { if (shouldUseNative(settings)) { return; } instance._observer = new IntersectionObserver((entries) => { intersectionHandler(entries, settings, instance); }, getObserverSettings(settings)); }; ================================================ FILE: src/lazyload.js ================================================ import { getExtendedSettings } from "./defaults"; import { autoInitialize } from "./autoInitialize"; import { load } from "./load"; import { setObserver, updateObserver } from "./intersectionObserver"; import { isBot, runningOnBrowser } from "./environment"; import { loadAllNative, shouldUseNative } from "./native"; import { resetOnlineCheck, setOnlineCheck } from "./online"; import { getElementsToLoad, queryElements } from "./dom"; import { resetStatus } from "./data"; import { setToLoadCount } from "./counters"; import { unobserve } from "./unobserve"; import { restore } from "./restore"; import { deleteOriginalAttrs } from "./originalAttributes"; const LazyLoad = function(customSettings, elements) { const settings = getExtendedSettings(customSettings); this._settings = settings; this.loadingCount = 0; setObserver(settings, this); setOnlineCheck(settings, this); this.update(elements); }; LazyLoad.prototype = { update: function(givenNodeset) { const settings = this._settings; const elementsToLoad = getElementsToLoad(givenNodeset, settings); setToLoadCount(this, elementsToLoad.length); if (isBot) { this.loadAll(elementsToLoad); return; } if (shouldUseNative(settings)) { loadAllNative(elementsToLoad, settings, this); return; } updateObserver(this._observer, elementsToLoad); }, destroy: function() { // Observer if (this._observer) { this._observer.disconnect(); } // Clean handlers resetOnlineCheck(this); // Clean custom attributes on elements queryElements(this._settings).forEach((element) => { deleteOriginalAttrs(element); }); // Delete all internal props delete this._observer; delete this._settings; delete this._onlineHandler; delete this.loadingCount; delete this.toLoadCount; }, loadAll: function(elements) { const settings = this._settings; const elementsToLoad = getElementsToLoad(elements, settings); elementsToLoad.forEach((element) => { unobserve(element, this); load(element, settings, this); }); }, restoreAll: function() { const settings = this._settings; queryElements(settings).forEach((element) => { restore(element, settings); }); } }; LazyLoad.load = (element, customSettings) => { const settings = getExtendedSettings(customSettings); load(element, settings); }; LazyLoad.resetStatus = (element) => { resetStatus(element); }; // Automatic instances creation if required (useful for async script loading) if (runningOnBrowser) { autoInitialize(LazyLoad, window.lazyLoadOptions); } export default LazyLoad; ================================================ FILE: src/load.js ================================================ import { setBackground, setImgsetBackground, setMultiBackground, setSources, setSourcesNative } from "./set"; import { setStatus } from "./data"; import { addOneShotEventListeners, hasLoadEvent } from "./event"; import { statusNative } from "./elementStatus"; import { addTempImage } from "./tempImage"; import { saveOriginalBackgroundStyle } from "./originalAttributes"; const loadBackground = (element, settings, instance) => { addTempImage(element); addOneShotEventListeners(element, settings, instance); saveOriginalBackgroundStyle(element); setBackground(element, settings, instance); setMultiBackground(element, settings, instance); setImgsetBackground(element, settings, instance); }; const loadRegular = (element, settings, instance) => { addOneShotEventListeners(element, settings, instance); setSources(element, settings, instance); }; export const load = (element, settings, instance) => { if (hasLoadEvent(element)) { loadRegular(element, settings, instance); } else { loadBackground(element, settings, instance); } }; export const loadNative = (element, settings, instance) => { element.setAttribute("loading", "lazy"); addOneShotEventListeners(element, settings, instance); setSourcesNative(element, settings); setStatus(element, statusNative); }; ================================================ FILE: src/native.js ================================================ import { loadNative } from "./load"; import { setToLoadCount } from "./counters"; const tagsWithNativeLazy = ["IMG", "IFRAME", "VIDEO"]; export const shouldUseNative = (settings) => settings.use_native && "loading" in HTMLImageElement.prototype; export const loadAllNative = (elements, settings, instance) => { elements.forEach((element) => { if (tagsWithNativeLazy.indexOf(element.tagName) === -1) { return; } loadNative(element, settings, instance); }); setToLoadCount(instance, 0); }; ================================================ FILE: src/online.js ================================================ import { runningOnBrowser } from "./environment"; import { resetStatus } from "./data"; import { removeClass } from "./class"; import { filterErrorElements, queryElements } from "./dom"; export const retryLazyLoad = (settings, instance) => { const errorElements = filterErrorElements(queryElements(settings)); errorElements.forEach((element) => { removeClass(element, settings.class_error); resetStatus(element); }); instance.update(); }; export const setOnlineCheck = (settings, instance) => { if (!runningOnBrowser) { return; } instance._onlineHandler = () => { retryLazyLoad(settings, instance); }; window.addEventListener("online", instance._onlineHandler); }; export const resetOnlineCheck = (instance) => { if (!runningOnBrowser) { return; } window.removeEventListener("online", instance._onlineHandler); }; ================================================ FILE: src/originalAttributes.js ================================================ import { DATA, ORIGINALS, POSTER, SIZES, SRC, SRCSET } from "./constants.js"; export const attrsSrc = [SRC]; export const attrsSrcPoster = [SRC, POSTER]; export const attrsSrcSrcsetSizes = [SRC, SRCSET, SIZES]; export const attrsData = [DATA]; export const hasOriginalAttrs = (element) => !!element[ORIGINALS]; export const getOriginalAttrs = (element) => element[ORIGINALS]; export const deleteOriginalAttrs = (element) => delete element[ORIGINALS]; // ## SAVE ## export const setOriginalsObject = (element, attributes) => { if (hasOriginalAttrs(element)) { return; } const originals = {}; attributes.forEach((attribute) => { originals[attribute] = element.getAttribute(attribute); }); element[ORIGINALS] = originals; }; export const saveOriginalBackgroundStyle = (element) => { if (hasOriginalAttrs(element)) { return; } element[ORIGINALS] = { backgroundImage: element.style.backgroundImage }; }; // ## RESTORE ## const setOrResetAttribute = (element, attrName, value) => { if (!value) { element.removeAttribute(attrName); return; } element.setAttribute(attrName, value); }; export const restoreOriginalAttrs = (element, attributes) => { if (!hasOriginalAttrs(element)) { return; } const originals = getOriginalAttrs(element); attributes.forEach((attribute) => { setOrResetAttribute(element, attribute, originals[attribute]); }); }; export const restoreOriginalBgImage = (element) => { if (!hasOriginalAttrs(element)) { return; } const originals = getOriginalAttrs(element); element.style.backgroundImage = originals.backgroundImage; }; ================================================ FILE: src/reset.js ================================================ import { SIZES, SRC, SRCSET } from "./constants.js"; import { forEachPictureSource } from "./forEachSource"; const removeImageAttributes = (element) => { element.removeAttribute(SRC); element.removeAttribute(SRCSET); element.removeAttribute(SIZES); }; export const resetSourcesImg = (element) => { forEachPictureSource(element, (sourceTag) => { removeImageAttributes(sourceTag); }); removeImageAttributes(element); }; ================================================ FILE: src/restore.js ================================================ import { removeClass } from "./class"; import { hasEmptyStatus, hasStatusNative, resetStatus } from "./data"; import { forEachPictureSource, forEachVideoSource } from "./forEachSource"; import { attrsData, attrsSrc, attrsSrcPoster, attrsSrcSrcsetSizes, deleteOriginalAttrs, restoreOriginalAttrs, restoreOriginalBgImage } from "./originalAttributes"; export const restoreImg = (imgEl) => { forEachPictureSource(imgEl, (sourceEl) => { restoreOriginalAttrs(sourceEl, attrsSrcSrcsetSizes); }); restoreOriginalAttrs(imgEl, attrsSrcSrcsetSizes); }; export const restoreVideo = (videoEl) => { forEachVideoSource(videoEl, (sourceEl) => { restoreOriginalAttrs(sourceEl, attrsSrc); }); restoreOriginalAttrs(videoEl, attrsSrcPoster); videoEl.load(); }; export const restoreIframe = (iframeEl) => { restoreOriginalAttrs(iframeEl, attrsSrc); }; export const restoreObject = (objectEl) => { restoreOriginalAttrs(objectEl, attrsData); }; const restoreFunctions = { IMG: restoreImg, IFRAME: restoreIframe, VIDEO: restoreVideo, OBJECT: restoreObject }; const restoreAttributes = (element) => { const restoreFunction = restoreFunctions[element.tagName]; if (!restoreFunction) { restoreOriginalBgImage(element); return; } restoreFunction(element); }; const resetClasses = (element, settings) => { if (hasEmptyStatus(element) || hasStatusNative(element)) { return; } removeClass(element, settings.class_entered); removeClass(element, settings.class_exited); removeClass(element, settings.class_applied); removeClass(element, settings.class_loading); removeClass(element, settings.class_loaded); removeClass(element, settings.class_error); }; export const restore = (element, settings) => { restoreAttributes(element); resetClasses(element, settings); resetStatus(element); deleteOriginalAttrs(element); }; ================================================ FILE: src/set.js ================================================ import { DATA, POSTER, SIZES, SRC, SRCSET } from "./constants.js"; import { getData, setStatus } from "./data"; import { statusApplied, statusLoading } from "./elementStatus"; import { safeCallback } from "./callback"; import { addClass } from "./class"; import { getTempImage } from "./tempImage"; import { isHiDpi } from "./environment"; import { unobserve } from "./unobserve"; import { updateLoadingCount } from "./counters"; import { forEachPictureSource, forEachVideoSource } from "./forEachSource"; import { attrsData, attrsSrc, attrsSrcPoster, attrsSrcSrcsetSizes, setOriginalsObject } from "./originalAttributes"; export const manageApplied = (element, settings, instance) => { addClass(element, settings.class_applied); setStatus(element, statusApplied); // Instance is not provided when loading is called from static class if (!instance) return; if (settings.unobserve_completed) { // Unobserve now because we can't do it on load unobserve(element, settings, instance); } safeCallback(settings.callback_applied, element, instance); }; export const manageLoading = (element, settings, instance) => { addClass(element, settings.class_loading); setStatus(element, statusLoading); // Instance is not provided when loading is called from static class if (!instance) return; updateLoadingCount(instance, +1); safeCallback(settings.callback_loading, element, instance); }; export const setAttributeIfValue = (element, attrName, value) => { if (!value) { return; } element.setAttribute(attrName, value); }; export const setImageAttributes = (element, settings) => { setAttributeIfValue(element, SIZES, getData(element, settings.data_sizes)); setAttributeIfValue(element, SRCSET, getData(element, settings.data_srcset)); setAttributeIfValue(element, SRC, getData(element, settings.data_src)); }; export const setSourcesImg = (imgEl, settings) => { forEachPictureSource(imgEl, (sourceTag) => { setOriginalsObject(sourceTag, attrsSrcSrcsetSizes); setImageAttributes(sourceTag, settings); }); setOriginalsObject(imgEl, attrsSrcSrcsetSizes); setImageAttributes(imgEl, settings); }; export const setSourcesIframe = (iframe, settings) => { setOriginalsObject(iframe, attrsSrc); setAttributeIfValue(iframe, SRC, getData(iframe, settings.data_src)); }; export const setSourcesVideo = (videoEl, settings) => { forEachVideoSource(videoEl, (sourceEl) => { setOriginalsObject(sourceEl, attrsSrc); setAttributeIfValue(sourceEl, SRC, getData(sourceEl, settings.data_src)); }); setOriginalsObject(videoEl, attrsSrcPoster); setAttributeIfValue(videoEl, POSTER, getData(videoEl, settings.data_poster)); setAttributeIfValue(videoEl, SRC, getData(videoEl, settings.data_src)); videoEl.load(); }; export const setSourcesObject = (object, settings) => { setOriginalsObject(object, attrsData); setAttributeIfValue(object, DATA, getData(object, settings.data_src)); }; export const setBackground = (element, settings, instance) => { const bg1xValue = getData(element, settings.data_bg); const bgHiDpiValue = getData(element, settings.data_bg_hidpi); const bgDataValue = isHiDpi && bgHiDpiValue ? bgHiDpiValue : bg1xValue; if (!bgDataValue) return; element.style.backgroundImage = `url("${bgDataValue}")`; getTempImage(element).setAttribute(SRC, bgDataValue); manageLoading(element, settings, instance); }; // NOTE: THE TEMP IMAGE TRICK CANNOT BE DONE WITH data-multi-bg // BECAUSE INSIDE ITS VALUES MUST BE WRAPPED WITH URL() AND ONE OF THEM // COULD BE A GRADIENT BACKGROUND IMAGE export const setMultiBackground = (element, settings, instance) => { const bg1xValue = getData(element, settings.data_bg_multi); const bgHiDpiValue = getData(element, settings.data_bg_multi_hidpi); const bgDataValue = isHiDpi && bgHiDpiValue ? bgHiDpiValue : bg1xValue; if (!bgDataValue) { return; } element.style.backgroundImage = bgDataValue; manageApplied(element, settings, instance); }; export const setImgsetBackground = (element, settings, instance) => { const bgImgSetDataValue = getData(element, settings.data_bg_set); if (!bgImgSetDataValue) { return; } const imgSetValues = bgImgSetDataValue.split("|"); let bgImageValues = imgSetValues.map((value) => `image-set(${value})`); element.style.backgroundImage = bgImageValues.join(); manageApplied(element, settings, instance); }; const setSourcesFunctions = { IMG: setSourcesImg, IFRAME: setSourcesIframe, VIDEO: setSourcesVideo, OBJECT: setSourcesObject }; export const setSourcesNative = (element, settings) => { const setSourcesFunction = setSourcesFunctions[element.tagName]; if (!setSourcesFunction) { return; } setSourcesFunction(element, settings); }; export const setSources = (element, settings, instance) => { const setSourcesFunction = setSourcesFunctions[element.tagName]; if (!setSourcesFunction) { return; } setSourcesFunction(element, settings); manageLoading(element, settings, instance); }; ================================================ FILE: src/tempImage.js ================================================ export const addTempImage = (element) => { element.llTempImage = document.createElement("IMG"); }; export const deleteTempImage = (element) => { delete element.llTempImage; }; export const getTempImage = (element) => element.llTempImage; ================================================ FILE: src/unobserve.js ================================================ export const unobserve = (element, instance) => { if (!instance) return; const observer = instance._observer; if (!observer) return; observer.unobserve(element); }; export const resetObserver = (observer) => { observer.disconnect(); }; export const unobserveEntered = (element, settings, instance) => { if (settings.unobserve_entered) unobserve(element, instance); }; ================================================ FILE: tests/e2e/background_image.spec.js ================================================ import { expect, test } from "@playwright/test"; const pagesWithSimpleImages = [ { url: "/demos/background_images.html", description: "Background images" } ]; for (const { url, description } of pagesWithSimpleImages) { test(description, async ({ page }) => { await page.goto(url); const lazyElements = await page.locator(".lazy"); await page.waitForLoadState("load"); const elementsCount = await lazyElements.count(); const devicePixelRatio = await page.evaluate(() => window.devicePixelRatio); // Eventually scroll into view and check if it loads for (let i = 0; i < elementsCount; i++) { const element = lazyElements.nth(i); await element.scrollIntoViewIfNeeded(); // Set expectations const dataBg = await element.getAttribute("data-bg"); const dataBgHidpi = await element.getAttribute("data-bg-hidpi"); const expectedBg = `background-image: url("${dataBg}");`; const expectedHiDpiBg = `background-image: url("${dataBgHidpi}");`; if (!dataBgHidpi) { await expect(element).toHaveAttribute("style", expectedBg); continue; } if (devicePixelRatio > 1) { await expect(element).toHaveAttribute("style", expectedHiDpiBg); } else { await expect(element).toHaveAttribute("style", expectedBg); } } }); } ================================================ FILE: tests/e2e/background_image_multi.spec.js ================================================ import { expect, test } from "@playwright/test"; const pagesWithSimpleImages = [ { url: "/demos/background_images_multi.html", description: "Multiple background images" } ]; for (const { url, description } of pagesWithSimpleImages) { test(description, async ({ page }) => { await page.goto(url); const lazyElements = await page.locator(".lazy"); await page.waitForLoadState("load"); const elementsCount = await lazyElements.count(); const devicePixelRatio = await page.evaluate(() => window.devicePixelRatio); // Eventually scroll into view and check if it loads for (let i = 0; i < elementsCount; i++) { const element = lazyElements.nth(i); await element.scrollIntoViewIfNeeded(); // Set expectations const dataBgMulti = await element.getAttribute("data-bg-multi"); const dataBgMultiHidpi = await element.getAttribute("data-bg-multi-hidpi"); const expectedBg = `background-image: ${dataBgMulti};`; const expectedHiDpiBg = `background-image: ${dataBgMultiHidpi};`; if (!dataBgMultiHidpi) { await expect(element).toHaveAttribute("style", expectedBg); continue; } if (devicePixelRatio > 1) { await expect(element).toHaveAttribute("style", expectedHiDpiBg); } else { await expect(element).toHaveAttribute("style", expectedBg); } } }); } ================================================ FILE: tests/e2e/image_basic.spec.js ================================================ import { expect, test } from "@playwright/test"; const pagesWithSimpleImages = [ { url: "/demos/image_basic.html", description: "Basic usage with image" }, { url: "/demos/image_ph_inline.html", description: "With inline placeholder image" }, { url: "/demos/image_ph_external.html", description: "With external placeholder image" }, { url: "/demos/async.html", description: "Async initialization" }, { url: "/demos/async_multiple.html", description: "Async initialization - multiple instances" }, ]; for (const { url, description } of pagesWithSimpleImages) { test(description, async ({ page }) => { await page.goto(url); const lazyImages = await page.locator(".lazy"); await page.waitForLoadState("load"); const imageCount = await lazyImages.count(); // Eventually scroll into view and check if it loads for (let i = 0; i < imageCount; i++) { const image = lazyImages.nth(i); await image.scrollIntoViewIfNeeded(); // Check the src attribute const expectedSrc = await image.getAttribute("data-src"); await expect(image).toHaveAttribute("src", expectedSrc); } }); } ================================================ FILE: tests/unit/cancelOnExit.test.js ================================================ import expectExtend from "./lib/expectExtend"; import getFakeInstance from "./lib/getFakeInstance"; import { expect, beforeEach, afterEach, describe, test } from "@jest/globals"; import { cancelLoading } from "../../src/cancelOnExit"; import { getExtendedSettings } from "../../src/defaults"; import { getStatus, setStatus } from "../../src/data"; import { statusLoaded } from "../../src/elementStatus"; import { setSources } from "../../src/set"; expectExtend(expect); var outerDiv, instance, settings; beforeEach(() => { outerDiv = document.createElement("div"); settings = getExtendedSettings(); instance = getFakeInstance(); }); afterEach(() => { outerDiv = null; settings = null; instance = null; }); describe("Cancel loading on img", () => { let img; const url1 = "1.gif"; const url100 = "100.gif"; const url200 = "200.gif"; const sizes100 = "100vw"; const entry = "fake-entry"; beforeEach(() => { outerDiv.appendChild((img = document.createElement("img"))); }); afterEach(() => { outerDiv.removeChild(img); img = null; }); test("Does nothing if cancel_on_exit is false", () => { settings.cancel_on_exit = false; img.setAttribute("src", url1); img.setAttribute("data-src", url200); setSources(img, settings); cancelLoading(img, entry, settings, instance); expect(img).toHaveAttribute("src", url200); }); test("Does nothing if element is not loading", () => { img.setAttribute("src", url200); setSources(img, settings); setStatus(img, statusLoaded); cancelLoading(img, entry, settings, instance); expect(img).toHaveAttribute("src", url200); }); test("Restores original attributes", () => { img.setAttribute("src", url1); img.setAttribute("data-src", url100); img.setAttribute("data-srcset", url200); img.setAttribute("data-sizes", sizes100); setSources(img, settings, instance); cancelLoading(img, entry, settings, instance); expect(img).toHaveAttribute("src", url1); expect(img).not.toHaveAttribute("srcset"); expect(img).not.toHaveAttribute("sizes"); }); test("Removes loading class", () => { img.setAttribute("src", url200); setSources(img, settings); cancelLoading(img, entry, settings, instance); expect(img.className).toBe(""); }); test("Decreases loading count", () => { img.setAttribute("src", url200); setSources(img, settings, instance); cancelLoading(img, entry, settings, instance); expect(instance.loadingCount).toBe(0); }); test("Resets internal status", () => { img.setAttribute("src", url200); setSources(img, settings, instance); cancelLoading(img, entry, settings, instance); expect(getStatus(img)).toBe(null); }); test("Callbacks are called", () => { const cancelCb = jest.fn(); settings.callback_cancel = cancelCb; setSources(img, settings, instance); cancelLoading(img, entry, settings, instance); expect(cancelCb).toHaveBeenCalledTimes(1); expect(cancelCb).toHaveBeenCalledWith(img, entry, instance); }); }); describe("Cancel loading on iframe", () => { let iframe; const iframeSrc = "https://github.com"; const entry = "fake-entry"; beforeEach(() => { outerDiv.appendChild((iframe = document.createElement("iframe"))); }); afterEach(() => { outerDiv.removeChild(iframe); iframe = null; }); test("Doesn't cancel loading", () => { iframe.setAttribute("data-src", iframeSrc); setSources(iframe, settings, instance); cancelLoading(iframe, entry, settings, instance); expect(iframe).toHaveAttribute("src", iframeSrc); }); }); ================================================ FILE: tests/unit/lib/expectExtend.js ================================================ const extensions = { toHaveAttributeValue: (element, attributeName, valueToVerify) => { const actualValue = element.getAttribute(attributeName); const pass = actualValue === valueToVerify; return pass ? { message: () => `${element.tagName} has attribute "${attributeName}" set to "${valueToVerify}"`, pass: true } : { message: () => `expected ${element.tagName} to have attribute "${attributeName}" set to "${valueToVerify}", received "${actualValue}"`, pass: false }; }, toHaveAttribute: (element, attributeName) => { const pass = element.hasAttribute(attributeName); return pass ? { message: () => `${element.tagName} has attribute "${attributeName}"" with value "${element.getAttribute(attributeName)}"`, pass: true } : { message: () => `expected ${element.tagName} to have attribute "${attributeName}"`, pass: false }; }, toHaveClassName: (element, className) => { const pass = element.classList.contains(className); return pass ? { message: () => `${element.tagName} has class "${className}"`, pass: true } : { message: () => `expected ${element.tagName} to have class "${className}"`, pass: false }; } }; export default (expect) => { expect.extend(extensions); }; ================================================ FILE: tests/unit/lib/getFakeInstance.js ================================================ export default () => { return { loadingCount: 0, toLoadCount: 0 }; }; ================================================ FILE: tests/unit/load.test.js ================================================ import expectExtend from "./lib/expectExtend"; import getFakeInstance from "./lib/getFakeInstance"; import { expect, beforeEach, afterEach, describe, test } from "@jest/globals"; import { load } from "../../src/load"; import { getExtendedSettings } from "../../src/defaults"; import { getStatus } from "../../src/data"; import { statusLoading } from "../../src/elementStatus"; expectExtend(expect); var outerDiv, settings, instance; beforeEach(() => { outerDiv = document.createElement("div"); settings = getExtendedSettings(); instance = getFakeInstance(); }); afterEach(() => { outerDiv = null; settings = null; instance = null; }); describe("load...", () => { let img; beforeEach(() => { outerDiv.appendChild((img = document.createElement("img"))); }); afterEach(() => { outerDiv.removeChild(img); img = null; }); test("status is set", () => { load(img, {}); const status = getStatus(img); expect(status).toBe(statusLoading); }); test("callbacks are called", () => { const loadingCb = jest.fn(); settings.callback_loading = loadingCb; load(img, settings, instance); expect(loadingCb).toHaveBeenCalledTimes(1); expect(loadingCb).toHaveBeenCalledWith(img, instance); }); }); ================================================ FILE: tests/unit/originalAttributes.test.js ================================================ import expectExtend from "./lib/expectExtend"; import getFakeInstance from "./lib/getFakeInstance"; import { getExtendedSettings } from "../../src/defaults"; import { expect, beforeEach, afterEach, describe, test } from "@jest/globals"; import { setSources } from "../../src/set"; import { getOriginalAttrs } from "../../src/originalAttributes"; expectExtend(expect); var outerDiv, settings, instance; beforeEach(() => { outerDiv = document.createElement("div"); settings = getExtendedSettings(); instance = getFakeInstance(); }); afterEach(() => { outerDiv = null; settings = null; instance = null; }); describe("Original attributes for images", () => { let img; const url1 = "1.gif"; const url2 = "2.gif"; const url200 = "200.gif"; const url400 = "400.gif"; const sizes100 = "100vw"; const sizes50 = "50vw"; beforeEach(() => { outerDiv.appendChild((img = document.createElement("img"))); }); afterEach(() => { outerDiv.removeChild(img); img = null; }); test("are saved correctly", () => { img.setAttribute("src", url1); img.setAttribute("srcset", url2); img.setAttribute("sizes", sizes100); img.setAttribute("data-src", url200); img.setAttribute("data-srcset", url400); img.setAttribute("data-sizes", sizes50); setSources(img, settings, instance); const originals = getOriginalAttrs(img); expect(originals.src).toBe(url1); expect(originals.srcset).toBe(url2); expect(originals.sizes).toBe(sizes100); }); }); // TODO: ADD MORE TESTS FOR IFRAMES, VIDEOS, PICTURE, and BG IMAGES ================================================ FILE: tests/unit/reset.test.js ================================================ import expectExtend from "./lib/expectExtend"; import getFakeInstance from "./lib/getFakeInstance"; import { getExtendedSettings } from "../../src/defaults"; import { expect, beforeEach, afterEach, describe, test } from "@jest/globals"; import { resetSourcesImg } from "../../src/reset"; expectExtend(expect); var outerDiv, settings, instance; beforeEach(() => { outerDiv = document.createElement("div"); settings = getExtendedSettings(); instance = getFakeInstance(); }); afterEach(() => { outerDiv = null; settings = null; instance = null; }); describe("resetSourcesImg", () => { let img; const url1 = "1.gif"; const url200 = "200.gif"; const sizes50 = "50vw"; beforeEach(() => { outerDiv.appendChild((img = document.createElement("img"))); }); afterEach(() => { outerDiv.removeChild(img); img = null; }); test("with initially empty src and srcset", () => { img.setAttribute("src", url1); img.setAttribute("srcset", url200); img.setAttribute("sizes", sizes50); resetSourcesImg(img); expect(img).not.toHaveAttribute("src"); expect(img).not.toHaveAttribute("srcset"); expect(img).not.toHaveAttribute("sizes"); }); }); ================================================ FILE: tests/unit/restore.test.js ================================================ import expectExtend from "./lib/expectExtend"; import getFakeInstance from "./lib/getFakeInstance"; import { beforeEach, afterEach, describe, expect, test } from "@jest/globals"; import { getExtendedSettings } from "../../src/defaults"; import { restore } from "../../src/restore"; import { load } from "../../src/load"; import { getStatus } from "../../src/data"; const url1 = "1.gif"; const url2 = "2.gif"; const url100 = "100.gif"; const url200 = "200.gif"; const url400 = "400.gif"; const sizes50 = "50vw"; var outerDiv, settings, instance; expectExtend(expect); beforeEach(() => { outerDiv = document.createElement("div"); settings = getExtendedSettings(); instance = getFakeInstance(); }); afterEach(() => { outerDiv = null; settings = null; instance = null; }); describe("restore for image", () => { let img; beforeEach(() => { outerDiv.appendChild((img = document.createElement("img"))); }); afterEach(() => { outerDiv.removeChild(img); img = null; }); test("when load hasn't been called on the element", () => { restore(img); expect(img).not.toHaveAttribute("src"); expect(img).not.toHaveAttribute("srcset"); expect(img).not.toHaveAttribute("sizes"); }); test("with initially empty src and srcset", () => { img.setAttribute("data-src", url200); img.setAttribute("data-srcset", url400); img.setAttribute("data-sizes", sizes50); load(img, settings, instance); restore(img, settings); expect(img).not.toHaveAttribute("src"); expect(img).not.toHaveAttribute("srcset"); expect(img).not.toHaveAttribute("sizes"); }); test("with initial values in src and srcset", () => { img.setAttribute("src", url1); img.setAttribute("srcset", url2); img.setAttribute("data-srcset", url400); img.setAttribute("data-src", url200); load(img, settings, instance); restore(img, settings); expect(img).toHaveAttributeValue("src", url1); expect(img).toHaveAttributeValue("srcset", url2); }); test("with initial values in src and srcset and empty data-*", () => { img.setAttribute("data-src", ""); img.setAttribute("data-srcset", ""); img.setAttribute("src", url200); img.setAttribute("srcset", url400); load(img, settings, instance); restore(img, settings); expect(img).toHaveAttributeValue("src", url200); expect(img).toHaveAttributeValue("srcset", url400); }); test("has no classes nor status after restore", () => { img.setAttribute("data-src", ""); load(img, settings, instance); restore(img, settings); expect(img).not.toHaveClassName("applied"); expect(img).not.toHaveClassName("loading"); expect(img).not.toHaveClassName("loaded"); expect(img).not.toHaveClassName("error"); expect(img).not.toHaveClassName("entered"); expect(img).not.toHaveClassName("exited"); expect(getStatus(img)).toBeNull(); }); }); describe("restore for picture", () => { let picture, source1, source2, img; beforeEach(() => { outerDiv.appendChild((picture = document.createElement("picture"))); picture.appendChild((source1 = document.createElement("source"))); picture.appendChild((source2 = document.createElement("source"))); picture.appendChild((img = document.createElement("img"))); }); afterEach(() => { outerDiv.removeChild(picture); }); test("when load hasn't been called on the element", () => { restore(img); expect(img).not.toHaveAttribute("srcset"); expect(source1).not.toHaveAttribute("srcset"); expect(source2).not.toHaveAttribute("srcset"); }); test("with initially empty srcset", () => { source1.setAttribute("data-srcset", url200); source2.setAttribute("data-srcset", url400); load(img, settings, instance); restore(img, settings); expect(source1).not.toHaveAttribute("srcset"); expect(source2).not.toHaveAttribute("srcset"); }); test("with initial value in srcset", () => { source1.setAttribute("srcset", url1); source1.setAttribute("data-srcset", url200); source2.setAttribute("srcset", url2); source2.setAttribute("data-srcset", url400); load(img, settings, instance); restore(img, settings); expect(source1).toHaveAttributeValue("srcset", url1); expect(source2).toHaveAttributeValue("srcset", url2); }); test("with initial value in srcset and empty data-srcset", () => { source1.setAttribute("data-srcset", ""); source2.setAttribute("data-srcset", ""); source1.setAttribute("srcset", url200); source2.setAttribute("srcset", url400); load(img, settings, instance); restore(img, settings); expect(source1).toHaveAttributeValue("srcset", url200); expect(source2).toHaveAttributeValue("srcset", url400); }); test("has no classes nor status after restore", () => { img.setAttribute("data-src", url100); source1.setAttribute("data-srcset", url200); source2.setAttribute("data-srcset", url400); load(img, settings, instance); restore(img, settings); expect(img).not.toHaveClassName("applied"); expect(img).not.toHaveClassName("loading"); expect(img).not.toHaveClassName("loaded"); expect(img).not.toHaveClassName("error"); expect(img).not.toHaveClassName("entered"); expect(img).not.toHaveClassName("exited"); expect(getStatus(img)).toBeNull(); }); }); describe("restore for iframe", () => { let iframe; const srcToLoad = "http://www.google.it"; const preloadedSrc = srcToLoad + "/doodle"; beforeEach(() => { outerDiv.appendChild((iframe = document.createElement("iframe"))); }); afterEach(() => { outerDiv.removeChild(iframe); iframe = null; }); test("when load hasn't been called on the element", () => { restore(iframe); expect(iframe).not.toHaveAttribute("srcset"); }); test("with initially empty src", () => { iframe.setAttribute("data-src", srcToLoad); load(iframe, settings, instance); restore(iframe, settings); expect(iframe).not.toHaveAttribute("src"); }); test("with initial value in src", () => { iframe.setAttribute("src", preloadedSrc); iframe.setAttribute("data-src", srcToLoad); load(iframe, settings, instance); restore(iframe, settings); expect(iframe).toHaveAttributeValue("src", preloadedSrc); }); test("has no classes nor status after restore", () => { iframe.setAttribute("data-src", ""); load(iframe, settings, instance); restore(iframe, settings); expect(iframe).not.toHaveClassName("applied"); expect(iframe).not.toHaveClassName("loading"); expect(iframe).not.toHaveClassName("loaded"); expect(iframe).not.toHaveClassName("error"); expect(iframe).not.toHaveClassName("entered"); expect(iframe).not.toHaveClassName("exited"); expect(getStatus(iframe)).toBeNull(); }); }); describe("restore for single background image", () => { let innerDiv; // Note: BUG in JsDOM doesn't return `url("")` with quotes inside beforeEach(() => { outerDiv.appendChild((innerDiv = document.createElement("iframe"))); //innerDiv.llTempImage = document.createElement("iframe"); }); afterEach(() => { outerDiv.removeChild(innerDiv); innerDiv = null; }); test("when load hasn't been called on the element", () => { restore(innerDiv); expect(innerDiv.style.backgroundImage).toBe(""); }); test("with initially empty style attribute", () => { innerDiv.setAttribute("data-bg", `url(${url200})`); load(innerDiv, settings, instance); restore(innerDiv, settings); expect(innerDiv.style.backgroundImage).toBe(""); }); test("with initial valye in style attribute", () => { innerDiv.style.padding = "1px"; innerDiv.setAttribute("data-bg", `url(${url400})`); load(innerDiv, settings, instance); restore(innerDiv, settings); expect(innerDiv.style.backgroundImage).toBe(""); expect(innerDiv.style.padding).toBe("1px"); }); test("with initially present style and background", () => { innerDiv.style.padding = "1px"; innerDiv.style.backgroundImage = `url(${url400})`; innerDiv.setAttribute("data-bg", `url(${url200})`); load(innerDiv, settings, instance); restore(innerDiv, settings); expect(innerDiv.style.backgroundImage).toBe(`url(${url400})`); }); test("has no classes nor status after restore", () => { innerDiv.setAttribute("data-bg", `url(${url200})`); load(innerDiv, settings, instance); restore(innerDiv, settings); expect(innerDiv).not.toHaveClassName("applied"); expect(innerDiv).not.toHaveClassName("loading"); expect(innerDiv).not.toHaveClassName("loaded"); expect(innerDiv).not.toHaveClassName("error"); expect(innerDiv).not.toHaveClassName("entered"); expect(innerDiv).not.toHaveClassName("exited"); expect(getStatus(innerDiv)).toBeNull(); }); }); // TO DO FROM HERE describe("resetMultiBackground for multiple background image", () => { let innerDiv; beforeEach(() => { outerDiv.appendChild((innerDiv = document.createElement("div"))); }); afterEach(() => { outerDiv.removeChild(innerDiv); innerDiv = null; }); test("when load hasn't been called on the element", () => { restore(innerDiv); expect(innerDiv.style.backgroundImage).toBe(""); }); test("with initially empty style attribute", () => { innerDiv.setAttribute("data-bg-multi", `url(${url200})`); load(innerDiv, settings, instance); restore(innerDiv, settings); expect(innerDiv.style.backgroundImage).toBe(""); }); test("with initially present style attribute", () => { innerDiv.setAttribute("data-bg-multi", `url(${url400})`); innerDiv.style.padding = "1px"; load(innerDiv, settings, instance); restore(innerDiv, settings); expect(innerDiv.style.backgroundImage).toBe(""); expect(innerDiv.style.padding).toBe("1px"); }); test("with initially present style and background", () => { innerDiv.setAttribute("data-bg-multi", `url(${url200})`); innerDiv.style.padding = "1px"; innerDiv.style.backgroundImage = `url(${url400})`; load(innerDiv, settings, instance); restore(innerDiv, settings); expect(innerDiv.style.backgroundImage).toBe(`url(${url400})`); }); test("has no classes nor status after restore", () => { innerDiv.setAttribute("data-bg-multi", `url(${url200})`); load(innerDiv, settings, instance); restore(innerDiv, settings); expect(innerDiv).not.toHaveClassName("applied"); expect(innerDiv).not.toHaveClassName("loading"); expect(innerDiv).not.toHaveClassName("loaded"); expect(innerDiv).not.toHaveClassName("error"); expect(innerDiv).not.toHaveClassName("entered"); expect(innerDiv).not.toHaveClassName("exited"); expect(getStatus(innerDiv)).toBeNull(); }); }); describe("restore for video", () => { let video; const videoUrlMp4 = "foobar.mp4"; const videoUrlAvi = "foobar.avi"; const videoUrlWebm = "foobar.webm"; beforeEach(() => { outerDiv.appendChild((video = document.createElement("video"))); //JSDOM doesn't have the video.load() method, need to mock it video.load = () => { }; }); afterEach(() => { video.load = null; outerDiv.removeChild(video); video = null; }); test("when load hasn't been called on the element", () => { restore(video); expect(video).not.toHaveAttribute("src"); }); test("with initially empty src", () => { video.setAttribute("data-src", videoUrlAvi); load(video, settings, instance); restore(video, settings); expect(video).not.toHaveAttribute("src"); }); test("with initial value in src", () => { video.setAttribute("src", videoUrlMp4); video.setAttribute("data-src", videoUrlAvi); load(video, settings, instance); restore(video, settings); expect(video).toHaveAttributeValue("src", videoUrlMp4); }); test("with source elements", () => { let source1, source2; video.appendChild((source1 = document.createElement("source"))); video.appendChild((source2 = document.createElement("source"))); video.setAttribute("data-src", videoUrlAvi); source1.setAttribute("data-src", videoUrlMp4); source2.setAttribute("data-src", videoUrlWebm); load(video, settings, instance); restore(video, settings); expect(video).not.toHaveAttribute("src"); expect(source1).not.toHaveAttribute("src"); expect(source2).not.toHaveAttribute("src"); }); test("has no classes nor status after restore", () => { video.setAttribute("data-src", videoUrlAvi); load(video, settings, instance); restore(video, settings); expect(video).not.toHaveClassName("applied"); expect(video).not.toHaveClassName("loading"); expect(video).not.toHaveClassName("loaded"); expect(video).not.toHaveClassName("error"); expect(video).not.toHaveClassName("entered"); expect(video).not.toHaveClassName("exited"); expect(getStatus(video)).toBeNull(); }); }); ================================================ FILE: tests/unit/set.test.js ================================================ import expectExtend from "./lib/expectExtend"; import getFakeInstance from "./lib/getFakeInstance"; import { getExtendedSettings } from "../../src/defaults"; import { expect, beforeEach, afterEach, describe, test } from "@jest/globals"; import { setBackground, setMultiBackground, setSources } from "../../src/set"; import { decreaseToLoadCount, setToLoadCount } from "../../src/counters"; expectExtend(expect); var outerDiv, settings, instance; beforeEach(() => { outerDiv = document.createElement("div"); settings = getExtendedSettings(); instance = getFakeInstance(); }); afterEach(() => { outerDiv = null; settings = null; instance = null; }); describe("setCounters", () => { test('decreaseToLoadCount decreases toLoadCount by 1', () => { const mockInstance = { toLoadCount: 5 }; decreaseToLoadCount(mockInstance); expect(mockInstance.toLoadCount).toBe(4); }); test('setToLoadCount sets toLoadCount to the given value', () => { const mockInstance = {}; setToLoadCount(mockInstance, 10); expect(mockInstance.toLoadCount).toBe(10); }); }) describe("setSources for image", () => { let img; const url1 = "1.gif"; const url200 = "200.gif"; const url400 = "400.gif"; beforeEach(() => { outerDiv.appendChild((img = document.createElement("img"))); }); afterEach(() => { outerDiv.removeChild(img); img = null; }); test("with initially empty src and srcset", () => { img.setAttribute("data-src", url200); img.setAttribute("data-srcset", url400); setSources(img, settings, instance); expect(img).toHaveAttribute("src", url200); expect(img).toHaveAttribute("srcset", url400); }); test("with initial values in src and srcset", () => { img.setAttribute("data-src", url200); img.setAttribute("data-srcset", url400); img.setAttribute("src", url1); img.setAttribute("srcset", url1); setSources(img, settings, instance); expect(img).toHaveAttribute("src", url200); expect(img).toHaveAttribute("srcset", url400); }); test("with initial values in src and srcset and empty data-*", () => { img.setAttribute("data-src", ""); img.setAttribute("data-srcset", ""); img.setAttribute("src", url200); img.setAttribute("srcset", url400); setSources(img, settings, instance); expect(img).toHaveAttribute("src", url200); expect(img).toHaveAttribute("srcset", url400); }); }); describe("setSources for iframe", () => { let iframe; const srcToLoad = "http://www.google.it"; const preloadedSrc = srcToLoad + "/doodle"; beforeEach(() => { outerDiv.appendChild((iframe = document.createElement("iframe"))); }); afterEach(() => { outerDiv.removeChild(iframe); iframe = null; }); test("with initially empty src", () => { iframe.setAttribute("data-src", srcToLoad); setSources(iframe, settings, instance); expect(iframe).toHaveAttribute("src", srcToLoad); }); test("with initial value in src", () => { iframe.setAttribute("data-src", srcToLoad); iframe.setAttribute("src", preloadedSrc); setSources(iframe, settings, instance); expect(iframe).toHaveAttribute("src", srcToLoad); }); test("with initial value in src and empty data-src", () => { iframe.setAttribute("data-src", ""); iframe.setAttribute("src", preloadedSrc); setSources(iframe, settings, instance); expect(iframe).toHaveAttribute("src", preloadedSrc); }); }); describe("setBackground for single background image", () => { let innerDiv; const url100 = "100.gif"; const url200 = "200.gif"; beforeEach(() => { outerDiv.appendChild((innerDiv = document.createElement("div"))); innerDiv.llTempImage = document.createElement("img"); }); afterEach(() => { outerDiv.removeChild(innerDiv); innerDiv = null; }); test("with initially empty style attribute", () => { innerDiv.setAttribute("data-bg", url200); setBackground(innerDiv, settings, instance); // Test cheating: bug in JsDOM doesn't return the url("") with quotes inside expect(innerDiv.style.backgroundImage).toBe(`url(${url200})`); }); test("with initially present style attribute", () => { innerDiv.setAttribute("data-bg", url100); innerDiv.style = { padding: "1px" }; setBackground(innerDiv, settings, instance); // Test cheating: bug in JsDOM doesn't return the url("") with quotes inside expect(innerDiv.style.backgroundImage).toBe(`url(${url100})`); }); test("with initially present style and background", () => { innerDiv.setAttribute("data-bg", url200); innerDiv.style = { padding: "1px", backgroundImage: `url(${url100})` }; setBackground(innerDiv, settings, instance); // Test cheating: bug in JsDOM doesn't return the url("") with quotes inside expect(innerDiv.style.backgroundImage).toBe(`url(${url200})`); }); }); describe("setMultiBackground for multiple background image", () => { let innerDiv; const url100 = "100.gif"; const url200 = "200.gif"; beforeEach(() => { outerDiv.appendChild((innerDiv = document.createElement("div"))); }); afterEach(() => { outerDiv.removeChild(innerDiv); innerDiv = null; }); test("with initially empty style attribute", () => { innerDiv.setAttribute("data-bg-multi", `url(${url200})`); setMultiBackground(innerDiv, settings, instance); // Test cheating: bug in JsDOM doesn't return the url("") with quotes inside expect(innerDiv.style.backgroundImage).toBe(`url(${url200})`); }); test("with initially present style attribute", () => { innerDiv.setAttribute("data-bg-multi", `url(${url100})`); innerDiv.style = { padding: "1px" }; setMultiBackground(innerDiv, settings, instance); // Test cheating: bug in JsDOM doesn't return the url("") with quotes inside expect(innerDiv.style.backgroundImage).toBe(`url(${url100})`); }); test("with initially present style and background", () => { innerDiv.setAttribute("data-bg-multi", `url(${url200})`); innerDiv.style = { padding: "1px", backgroundImage: `url(${url100})` }; setMultiBackground(innerDiv, settings, instance); // Test cheating: bug in JsDOM doesn't return the url("") with quotes inside expect(innerDiv.style.backgroundImage).toBe(`url(${url200})`); }); }); describe("setSources for video", () => { let video, source1, source2; const videoUrlMp4 = "foobar.mp4"; const videoUrlAvi = "foobar.avi"; const videoUrlWebm = "foobar.webm"; beforeEach(() => { outerDiv.appendChild((video = document.createElement("video"))); /* video.appendChild(document.createElement("source")); video.appendChild(document.createElement("source")); */ }); afterEach(() => { outerDiv.removeChild(video); source1 = null; source2 = null; video = null; }); test("with initially empty src", () => { video.load = jest.fn(); video.setAttribute("data-src", videoUrlAvi); setSources(video, settings, instance); expect(video).toHaveAttribute("src", videoUrlAvi); expect(video.load).toHaveBeenCalled(); }); test("with source elements", () => { video.load = jest.fn(); video.setAttribute("data-src", videoUrlAvi); video.appendChild((source1 = document.createElement("source"))); video.appendChild((source2 = document.createElement("source"))); source1.setAttribute("data-src", videoUrlMp4); source2.setAttribute("data-src", videoUrlWebm); setSources(video, settings, instance); expect(video).toHaveAttribute("src", videoUrlAvi); expect(source1).toHaveAttribute("src", videoUrlMp4); expect(source2).toHaveAttribute("src", videoUrlWebm); expect(video.load).toHaveBeenCalled(); }); }); describe("setSources for picture", () => { let img, picture, source1, source2; const url1 = "1.gif"; const url200 = "200.gif"; const url400 = "400.gif"; beforeEach(() => { outerDiv.appendChild((picture = document.createElement("picture"))); picture.appendChild((source1 = document.createElement("source"))); picture.appendChild((source2 = document.createElement("source"))); picture.appendChild((img = document.createElement("img"))); }); afterEach(() => { outerDiv.removeChild(picture); picture = null; source1 = null; source2 = null; img = null; }); test("with initially empty srcset", () => { source1.setAttribute("data-srcset", url200); source2.setAttribute("data-srcset", url400); setSources(img, settings, instance); expect(source1).toHaveAttribute("srcset", url200); expect(source2).toHaveAttribute("srcset", url400); }); test("with initial value in srcset", () => { source1.setAttribute("data-srcset", url200); source2.setAttribute("data-srcset", url400); source1.setAttribute("srcset", url1); source2.setAttribute("srcset", url1); setSources(img, settings, instance); expect(source1).toHaveAttribute("srcset", url200); expect(source2).toHaveAttribute("srcset", url400); }); test("with initial value in srcset and empty data-srcset", () => { source1.setAttribute("data-srcset", ""); source2.setAttribute("data-srcset", ""); source1.setAttribute("srcset", url200); source2.setAttribute("srcset", url400); setSources(img, settings, instance); expect(source1).toHaveAttribute("srcset", url200); expect(source2).toHaveAttribute("srcset", url400); }); }); ================================================ FILE: todo.md ================================================ # TODO ## 1 Check why the demo `./demos/restore_destroy.html` is not working. What was it supposed to do? :D ## To consider - Check how LazyLoad behaves when a page was updated using DOM morphing. If only the data-attributes were updated, how do one forces LazyLoad to read from them again? - Check out how the plugin architecture of lazysizes work - Consider creating a subset of options and defaults for the static `load` method, and document it - [Edge case] When `cancel_on_exit` is `false`, unobserve elements as soon as they start loading (as of before 15.2.0). ## More tests Add more tests (to choose between unit tests or e2e tests depending on the case) to cover: - autoinitialize - purge - reveal ================================================ FILE: typings/lazyload.d.ts ================================================ export interface ILazyLoadOptions { /** * The CSS selector of the elements to load lazily, which will be selected * as descendants of the `container` object. * @default ".lazy" */ elements_selector?: string; /** * The scrolling container of the elements in the `elements_selector` option. * * @default document */ container?: HTMLElement; /** * A number of pixels representing the outer distance off the scrolling area * from which to start loading the elements. * @default 300 */ threshold?: number; /** * Similar to `threshold`, but accepting multiple values and both `px` and `%` * units. It maps directly to the `rootMargin` property of IntersectionObserver, * so it must be a string with a syntax similar to the CSS `margin` property. * You can use it when you need to have different thresholds for the scrolling * area. It overrides `threshold` when passed. * * @default null * * @see https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin */ thresholds?: string; /** * The name of the data attribute containing the element URL to load, * excluding the `"data-"` part. * E.g. if your data attribute is named `"data-src"`, * just pass `"src"` * * @default "src" */ data_src?: string; /** * The name of the data attribute containing the image URL set to load, * in either img and source tags, excluding the "data-" part. * E.g. if your data attribute is named `"data-srcset"`, * just pass `"srcset"` * * @default "srcset" */ data_srcset?: string; /** * The name of the data attribute containing the sizes attribute to use, * excluding the `"data-"` part. * E.g. if your data attribute is named `"data-sizes"`, just pass `"sizes"` * * @default "sizes" */ data_sizes?: string; /** * The name of the data attribute containing the URL of `background-image` * to load lazily, excluding the `"data-"` part. * E.g. if your data attribute is named `"data-bg"`, just pass `"bg"`. * The attribute value must be a valid value for `background-image`, * including the `url()` part of the CSS instruction. * * @default "bg" */ data_bg?: string; /** * The name of the data attribute containing the URL of `background-image` * to load lazily on HiDPI screens, excluding the `"data-"` part. * E.g. if your data attribute is named `"data-bg-hidpi"`, just pass `"bg-hidpi"`. * The attribute value must be a valid value for `background-image`, * including the `url()` part of the CSS instruction. * * @default "bg-hidpi" */ data_bg_hidpi?: string; /** * The name of the data attribute containing the value of multiple `background-image` * to load lazily, excluding the `"data-"` part. * E.g. if your data attribute is named `"data-bg-multi"`, just pass `"bg-multi"`. * The attribute value must be a valid value for `background-image`, * including the `url()` part of the CSS instruction. * * @default "bg-multi" */ data_bg_multi?: string; /** * The name of the data attribute containing the value of multiple `background-image` * to load lazily on HiDPI screens, excluding the `"data-"` part. * E.g. if your data attribute is named `"data-bg-multi-hidpi"`, just pass `"bg-multi-hidpi"`. * The attribute value must be a valid value for `background-image`, * including the `url()` part of the CSS instruction. * * @default "bg-multi-hidpi" */ data_bg_multi_hidpi?: string; /** * The name of the data attribute containing the value of the background to * be applied with image-set, excluding the `"data-"` part. * E.g. if your data attribute is named `"data-bg-set"`, just pass `"bg-set"`. * The attribute value must be what goes inside the `image-set` CSS function. * You can separate values with a pipe (`|`) character to have * multiple backgrounds. * * @default "bg-set" */ data_bg_set?: string; /** * The name of the data attribute containing the value of poster to load lazily, * excluding the `"data-"` part. * E.g. if your data attribute is named `"data-poster"`, just pass `"poster"`. * * @default "poster" */ data_poster?: string; /** * The class applied to the multiple background elements after the multiple * background was applied * * @default "applied" */ class_applied?: string; /** * The class applied to the elements while the loading is in progress. * * @default "loading" */ class_loading?: string; /** * The class applied to the elements when the loading is complete. * * @default "loaded" */ class_loaded?: string; /** * The class applied to the elements when the element causes an error. * * @default "error" */ class_error?: string; /** * The class applied to the elements after they entered the viewport. * * @default "entered" */ class_entered?: string; /** * The class applied to the elements after they exited the viewport. * * @default "exited" */ class_exited?: string; /** * A boolean that defines whether or not to automatically unobserve * elements once they've loaded or throwed an error * * @default true */ unobserve_completed?: boolean; /** * A boolean that defines whether or not to automatically unobserve * elements once they entered the viewport * * @default false */ unobserve_entered?: boolean; /** * A boolean that defines whether or not to cancel the download of the * images that exit the viewport while they are still loading, eventually * restoring the original attributes. It applies only to images so to the * `img` (and `picture`) tags, so it doesn't apply to background images, * `iframe`s nor `video`s. * * @default true */ cancel_on_exit?: boolean; /** * A callback function which is called whenever an element enters the viewport. * Arguments: DOM element, intersection observer entry, lazyload instance. */ callback_enter?: ( elt: HTMLElement, entry: IntersectionObserverEntry, instance: ILazyLoadInstance ) => void; /** * A callback function which is called whenever an element exits the viewport. * Arguments: `DOM element`, `intersection observer entry`, `lazyload instance`. */ callback_exit?: ( elt: HTMLElement, entry: IntersectionObserverEntry, instance: ILazyLoadInstance ) => void; /** * A callback function which is called whenever a multiple background * element starts loading. * Arguments: `DOM element`, `lazyload instance`. */ callback_applied?: (elt: HTMLElement, instance: ILazyLoadInstance) => void; /** * A callback function which is called whenever an element starts loading. * Arguments: `DOM element`, `lazyload instance`. */ callback_loading?: (elt: HTMLElement, instance: ILazyLoadInstance) => void; /** * A callback function which is called whenever an element finishes loading. * Note that, in version older than 11.0.0, this option went under the * name `callback_load`. * Arguments: `DOM element`, `lazyload instance`. */ callback_loaded?: (elt: HTMLElement, instance: ILazyLoadInstance) => void; /** * A callback function which is called whenever an element triggers an error. * Arguments: `DOM element`, `lazyload instance`. */ callback_error?: (elt: HTMLElement, instance: ILazyLoadInstance) => void; /** * A callback function which is called when there are no more elements to load and all elements have been downloaded. * Arguments: `lazyload instance`. */ callback_finish?: (instance: ILazyLoadInstance) => void; /** * A callback function which is called whenever an element loading is * canceled while loading, as for `cancel_on_exit: true` */ callback_cancel?: ( elt: HTMLElement, entry: IntersectionObserverEntry, instance: ILazyLoadInstance ) => void; /** * This boolean sets whether or not to use [native lazy loading](https://addyosmani.com/blog/lazy-loading/) * to do [hybrid lazy loading](https://www.smashingmagazine.com/2019/05/hybrid-lazy-loading-progressive-migration-native/). * On browsers that support it, LazyLoad will set the `loading="lazy"` attribute on `images` and `iframes`, * and delegate their loading to the browser. * * @default false */ use_native?: boolean; /** * Tells LazyLoad if to restore the original values of `src`, `srcset` and `sizes` * when a loading error occurs. * * @default false */ restore_on_error?: boolean; } export interface ILazyLoadInstance { /** * Make LazyLoad to re-check the DOM for `elements_selector` elements inside its `container`. * * ### Use case * * Update LazyLoad after you added or removed DOM elements to the page. */ update: (elements?: NodeListOf) => void; /** * Destroys the instance, unsetting instance variables and removing listeners. * * ### Use case * * Free up some memory. Especially useful for Single Page Applications. */ destroy: () => void; /** * Loads all the lazy elements right away and stop observing them, * no matter if they are inside or outside the viewport, * no matter if they are hidden or visible. * * ### Use case * * To load all the remaining elements in advance */ loadAll: () => void; /** * Restores DOM to its original state. Note that it doesn't destroy LazyLoad, * so you probably want to use it along with destroy(). * * ### Use case * * Reset the DOM before a soft page navigation (SPA) occures, e.g. using TurboLinks. */ restoreAll: () => void; /** * The number of elements that are currently downloading from the network * (limitedly to the ones managed by the instance of LazyLoad). * This is particularly useful to understand whether * or not is safe to destroy this instance of LazyLoad. */ loadingCount: number; /** * The number of elements that haven't been lazyloaded yet * (limitedly to the ones managed by the instance of LazyLoad) */ toLoadCount: number; } export interface ILazyLoad { new (options?: ILazyLoadOptions, elements?: NodeListOf): ILazyLoadInstance; /** * Immediately loads the lazy `element`. * You can pass your custom options in the settings parameter. * Note that the `elements_selector` option has no effect, * since you are passing the element as a parameter. * Also note that this method has effect only once on a specific `element`. * * ### Use case * * To load an `element` at mouseover or at any other event different than "entering the viewport" */ load(element: HTMLElement, settings: ILazyLoadOptions): void; /** * Resets the internal status of the given element. * * ### Use case * * To tell LazyLoad to consider this `element` again, for example if you changed * the `data-src` attribute after the previous `data-src` was loaded, * call this method, then call `update()`. */ resetStatus(element: HTMLElement): void; } declare var LazyLoad: ILazyLoad; export default LazyLoad; ================================================ FILE: utils/index.html ================================================ Self-host images generator

Self-host images generator

  • Stivaletti
  • Open toe
  • Sneakers & Tennis
  • Sneakers & Tennis shoes basse
  • Sneakers & Tennis shoes alte
  • Sneakers & Tennis shoes alte
  • Sneakers & Tennis shoes basse
  • Sneakers & Tennis shoes basse
  • Stivali
  • Stivali
  • Stivaletti
  • Stivaletti
  • Stivali
  • Stivaletti
  • Décolleté
  • Mocassini
  • Stivaletti
  • Décolleté
  • Décolleté
  • Francesine
  • Stivaletti
  • Décolleté
  • Mocassini
  • Mocassini
  • Stivali
  • Stivaletti
  • Stivaletti
  • Mocassini
  • Stivaletti
  • Stivaletti
  • Stivaletti
  • Stivaletti
  • Cuissardes
  • Décolleté
  • Stivaletti
  • Sneakers & Tennis shoes basse
  • Mocassini
  • Sneakers & Tennis shoes basse
  • Mocassini
  • Mocassini