├── .babelrc
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── .storybook
└── config.js
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
└── UPGRADING.md
├── package.json
├── src
├── components
│ ├── DefaultWrapper.js
│ ├── Lazy.js
│ ├── LazyChild.js
│ ├── LazyGroup.js
│ └── __tests__
│ │ ├── DefaultWrapper.spec.js
│ │ ├── Lazy.spec.js
│ │ ├── LazyGroup.spec.js
│ │ └── __snapshots__
│ │ ├── DefaultWrapper.spec.js.snap
│ │ ├── Lazy.spec.js.snap
│ │ └── LazyGroup.spec.js.snap
├── index.js
└── lib
│ └── wrap.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@researchgate/babel-preset-rg"]
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://EditorConfig.org
2 | # top-most EditorConfig file
3 | root = true
4 |
5 | [*]
6 | end_of_line = lf
7 | trim_trailing_whitespace = true
8 | insert_final_newline = false
9 | indent_style = space
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.{js,jsx,scss}]
15 | charset = utf-8
16 | insert_final_newline = true
17 | indent_size = 4
18 |
19 | [{*.json,.travis.yml,.*rc}]
20 | indent_size = 2
21 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "extends": "@researchgate/rg-react",
3 | "parser": "babel-eslint",
4 | "settings": {
5 | "import/extensions": [
6 | ".js",
7 | ".jsx"
8 | ],
9 | "react": {
10 | "version": "detect"
11 | }
12 | },
13 | "rules": {
14 | "semi": [
15 | "error",
16 | "never"
17 | ],
18 | "import/extensions": ["error", "always", { "js": "never", "jsx": "never" }],
19 | "react/jsx-boolean-value": "never"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log*
3 | yarn-error.log
4 | /lib
5 | coverage
6 | docs/static
7 | docs/favicon*
8 | docs/*.html
9 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": true,
3 | "jsxBracketSameLine": false,
4 | "overrides": [{
5 | "files": "*.json",
6 | "options": {
7 | "tabWidth": 2
8 | }
9 | }],
10 | "printWidth": 120,
11 | "semi": false,
12 | "singleQuote": true,
13 | "tabWidth": 4,
14 | "trailingComma": "all",
15 | "useTabs": false
16 | }
17 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Merri/react-lazy/f66de17aa0ef6c550106d7caa4ad02a320449c99/.storybook/config.js
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "10"
4 | - "6"
5 | cache: yarn
6 | deploy:
7 | local_dir: docs
8 | provider: pages
9 | skip_cleanup: true
10 | github_token: $GITHUB_TOKEN # Set in travis-ci.org dashboard
11 | on:
12 | branch: master
13 | condition: '"$TRAVIS_JOB_NUMBER" == *.1'
14 | before_install: yarn global add greenkeeper-lockfile@1
15 | before_script:
16 | - greenkeeper-lockfile-update
17 | # true needs to be removed after https://github.com/conventional-changelog/conventional-github-releaser/pull/47 is fixed
18 | - 'if [[ -n "$TRAVIS_TAG" ]] && [[ "$TRAVIS_JOB_NUMBER" == *.1 ]]; then yarn run create-github-release || true; fi'
19 | script:
20 | - yarn run lint
21 | - yarn test -- --ci --coverage
22 | - 'if [[ "$TRAVIS_JOB_NUMBER" == *.1 ]]; then yarn run build:storybook; fi'
23 | after_script: greenkeeper-lockfile-upload
24 | after_success:
25 | - bash <(curl -s https://codecov.io/bash)
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6 |
7 |
8 | ## [1.1.0] - 2019-04-26
9 |
10 | ### Fixed
11 | - Allow blocking lazy load in `onViewport` events by using `preventDefault()`
12 | - No longer re-render as `noscript` if setting `visible` to false after element has come to viewport
13 | - `component` propTypes have been fixed to `any`
14 | - `clientOnly` works now correctly with `LazyGroup`
15 |
16 | ### Changed
17 | - `ref` is now passed through
18 | - Build and test system has been replaced with a less clunky modern one
19 | - `Lazy`'s render method has been compacted to shorter code
20 |
21 | ### Removed
22 | - Support for old versions of React, now minimum of 16.4 required
23 |
24 |
25 | ## [1.0.3] - 2018-06-13
26 |
27 | ### Changed
28 | - Now uses `@researchgate/react-intersection-observer` instead of customized implementation
29 |
30 | ### Fixed
31 | - Case where `viewport` and `threshold` were not passed to `Observer` in `Lazy`
32 |
33 |
34 | ## [1.0.2] - 2018-05-31
35 |
36 | ### Changed
37 | - Size of final generated JavaScript has been reduced
38 | - Minor code speed optimizations
39 |
40 | ### Removed
41 | - `DefaultWrapper` and `LazyChild` are no longer exported
42 |
43 |
44 | ## [1.0.1] - 2018-05-30
45 |
46 | ### Changed
47 | - React version requirement to 16
48 |
49 | ### Removed
50 | - Symbol polyfill requirement
51 |
52 |
53 | ## [1.0.0] - 2018-05-30
54 |
55 | ### Added
56 | - `Observer` component
57 | - `threshold` prop to `Lazy` and `LazyGroup`
58 | - `viewport` prop to `Lazy` and `LazyGroup` which works like `root` of IntersectionObserver
59 |
60 | ### Changed
61 | - `cushion` prop works now like `marginRoot` of IntersectionObserver
62 | - `jsOnly` prop is now `clientOnly`
63 | - Viewport detection to use IntersectionObserver API with code heavily based on
64 | [@researchgate/react-intersection-observer](https://github.com/researchgate/react-intersection-observer)
65 | - `LazyGroup` no longer extends from `Lazy` as both use `Observer` now
66 | - `onViewport` receives intersection observer event as first parameter
67 |
68 | ### Removed
69 | - `checkElementsInViewport`
70 |
71 | ### Fixed
72 | - Production build no longer requires `PropTypes`
73 |
74 |
75 | ## [0.6.1] - 2018-05-26
76 |
77 | - Fix dependency issue with `uglifyjs-webpack-plugin`
78 |
79 |
80 | ## [0.6.0] - 2018-05-26
81 |
82 | - Update to React 16.4
83 | - Use throttle instead of debounce
84 | - Update to Webpack 4 and other latest packages (except for the tests because I am lazy)
85 |
86 |
87 | ## [0.5.1] - 2017-11-14
88 |
89 | - Update to React 16.1.1
90 | - Update to Node 8 LTS; run Travis in it and include a `package-lock.json`
91 | - Fix render mismatch in certain cases
92 |
93 |
94 | ## [0.5.0] - 2017-11-07
95 |
96 | - Update to React 16
97 | - Add `jsOnly` option as a solution for less [React 16.0.0 errors](https://github.com/facebook/react/issues/10993)
98 |
99 |
100 | ## [0.4.1] - 2017-09-14
101 |
102 | - Update dependencies
103 | - Fix linting issues
104 | - Add event listener helpers
105 | - Use passive listeners
106 | - Add wheel event to listened events
107 |
108 |
109 | ## [0.4.0] - 2017-07-23
110 |
111 | - Remove `imgWrapperComponent` as it's development went only half-way to completion
112 | - Add `LazyGroup`, `LazyChild` and `DefaultWrapper` components
113 |
114 |
115 | ## [0.3.1] - 2017-07-18
116 |
117 | - Fix non-`ltIE9` not loading images
118 |
119 |
120 | ## [0.3.0] - 2017-07-18
121 |
122 | - Disable IE conditional comment rendering by default
123 | - Add `ltIE9` to enable IE conditional comment rendering
124 | - Update dependencies
125 | - Fix `React.DOM.div` warnings
126 |
127 |
128 | ## [0.2.1] - 2017-05-12
129 |
130 | - Fix issue where images loaded when `display: none` was set to hide an element
131 | - Introduces issue where having lazy loading on `position: fixed` elements does not work
132 |
133 |
134 | ## [0.2.0] - 2017-05-12
135 |
136 | - Upgrade to React 15.5
137 | - Babel 6
138 | - Webpack 2
139 | - Convert from createClass to ES6 class
140 | - Modularize
141 | - Test Travis CI in Node 6.10
142 | - Update linting
143 | - Change `nodeName` to `component` and allow for more than string tags
144 | - Fix bug in cushion handling
145 | - Add support for `imgWrapperComponent` which renders given component around all noscripted img childs
146 | - No longer expose `verge`
147 |
148 |
149 | ## [0.1.0] - 2016-04-11
150 |
151 | - Upgrade to React 0.14
152 |
153 |
154 | ## [0.0.3] - 2015-09-11
155 |
156 | - Fix issue #1: require loaded client side only intended minified version due to wrong reference in package.json
157 |
158 |
159 | ## [0.0.2] - 2015-07-29
160 |
161 | - Use debounce instead of throttle.
162 | - Expose checkElementsInViewport to allow manual triggers.
163 |
164 |
165 | ## [0.0.1] - 2015-07-28
166 |
167 | - Initial release.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 - 2019 Vesa Piittinen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-lazy
2 | [](https://www.npmjs.com/package/react-lazy)
3 | [](https://travis-ci.org/Merri/react-lazy)
4 |
5 | > Lazy load your content without breaking the internet!
6 |
7 | Supports universal rendering including disabled JavaScript by using `noscript` elements that are also friendly to all
8 | search engines. Uses modern IntersectionObserver API using the excellent
9 | [@researchgate/react-intersection-observer](https://github.com/researchgate/react-intersection-observer).
10 |
11 | Also optionally supports displaying the content on IE8 and earlier by adding conditional comments to skip `noscript`
12 | elements.
13 |
14 | [View lazy loading demo](https://merri.github.io/react-lazy/)
15 |
16 | ```js
17 | npm install react-lazy
18 |
19 | import { Lazy } from 'react-lazy'
20 | // or
21 | import { LazyGroup } from 'react-lazy'
22 | ```
23 |
24 | You also need to polyfill `intersection-observer`! Use polyfill.io or `require('intersection-observer')`. Check
25 | [Can I use](https://caniuse.com/#feat=intersectionobserver) for browser support status. `Map` and `Set` are also
26 | required, but these are required by React as well.
27 |
28 |
29 | ----
30 |
31 | ## Why `react-lazy`?
32 |
33 | 1. Minimalistic and performant implementation with less dependencies than other solutions
34 | 2. You can choose between ease-of-use (LazyGroup) and do-it-yourself (Lazy)
35 | 3. The hard part of handling `noscript` is done for you
36 |
37 |
38 | ----
39 |
40 | ### Why lazy load content such as images?
41 |
42 | You want to save your bandwidth and/or server load. As a side effect you may also gain some performance benefits on
43 | client side, especially on mobile devices. However the main benefit (and main purpose) for you should always be the
44 | reduction of bandwidth/server load.
45 |
46 | Likely side effect of lazy loading is that user may see content flashing as it comes into view; sometimes with a lot of
47 | delay as it depends on connectivity. You can make the experience less flickery by adding a transition when image is
48 | loaded (a bit harder to develop) or by giving `Lazy` a large cushion (500 pixels or more) to load image before it is
49 | actually in the viewport. Using both strategies together is recommended. You can test the experience on your own site by
50 | dropping mobile connection to slow 3G.
51 |
52 | Chrome developer tools also has network throttling so you don't need to get yourself into a train to nowhere to test how
53 | well or poorly your site works in high latency conditions. However it is also recommended you do get yourself into a
54 | train to nowhere as it does good for your mind and soul to abandon the hectic although convenient city lifestyle every
55 | once in a while.
56 |
57 |
58 | ----
59 |
60 | ## Usage: ``
61 |
62 | ```jsx
63 | // curly brackets are required
64 | import { Lazy } from 'react-lazy'
65 |
66 | ...
67 |
68 |
69 |
70 |
71 | ```
72 |
73 | ```html
74 |
75 |
76 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | ```
86 |
87 |
88 | ----
89 |
90 | ## Component introduction
91 |
92 | There are two components: `` and ``.
93 |
94 | **Lazy** provides basic functionality for lazy loading: it keeps render in `noscript` element until it has come into
95 | viewport and then simply swaps render. **Everything** inside the component is wrapped into `noscript`. As the component
96 | is quite simple and generic it doesn't support many other things that provide convenience; for example, with images you
97 | have to write your own logic for handling `onError` and `onLoad` so that you can do things like trigger transitions as
98 | images are loaded, or change what to render instead of the image if loading the image fails.
99 |
100 | **LazyGroup** extends `Lazy` functionality by wrapping only specified component types inside `noscript`. So only the
101 | specified components like `img` or `iframe` elements are wrapped to `noscript`. Other elements are simply rendered
102 | as-is.
103 |
104 | The wrappable components (`img`s and `iframe`s by default) are also always wrapped inside another component. This custom
105 | component will receive information on whether loading the `img` or `iframe` has succeeded or failed, thus allowing a
106 | single place to control lifecycles as images or other content is loaded.
107 |
108 |
109 | ## Shared features
110 |
111 | These features are supported by both `` and ``.
112 |
113 |
114 | ### IntersectionObserver props
115 |
116 | - `viewport` (= `root` option)
117 | - `cushion` (= `rootMargin` option)
118 | - `threshold`
119 |
120 | These props work like you would expect them to work with IntersectionObserver.
121 |
122 |
123 | ### `clientOnly` prop
124 |
125 | Disables `noscript` element rendering, instead rendering no HTML for the contents in server side. This gives behavior
126 | similar to most other lazy loaders, which is why it is not enabled by default in `react-lazy`.
127 |
128 |
129 | ### `ltIE9` prop
130 |
131 | Renders Internet Explorer 8 friendly syntax by adding conditional comments around `noscript`, effectively hiding
132 | existance of the tag from IE8 and earlier. This allows for minimal legacy browser support, since it is highly unlikely
133 | anyone writes their JS to execute on IE8 anymore.
134 |
135 | Essentially this feature allows to render a visually non-broken page to users of legacy browsers, making it possible to
136 | give minimally acceptable user experience to users of browsers that should be dead.
137 |
138 | This means there is no lazy rendering on legacy browsers, images load immediately.
139 |
140 | This prop has no effect if `clientOnly` is enabled.
141 |
142 |
143 | ### `onLoad`
144 |
145 | - On `Lazy` triggers after removing `noscript` element.
146 | - On `LazyGroup` triggers after **all** wrapped child components `onLoad` or `onError` events have triggered.
147 |
148 | ```jsx
149 | ...
150 | ```
151 |
152 |
153 | ### `onViewport`
154 |
155 | Triggers before removing `noscript` elements. Given function receives IntersectionObserver event object.
156 |
157 |
158 | ### `visible`
159 |
160 | Allows you to manually tell if the element is actually visible to the user or not.
161 |
162 |
163 | ----
164 |
165 | ## ``
166 |
167 | `Lazy` works fine with single images, but sometimes you may want to have slightly more control or better performance
168 | when you know multiple images load at the same time (for example, a row of thumbnails). In this case it makes no sense
169 | to check each individual image's position in viewport when checking for just the container component will be good enough
170 | — and also less for a browser to execute.
171 |
172 | You can also use `Lazy` for multiple images, but there are some practical limitations such as the fact that everything
173 | inside `Lazy` is within `noscript` element, thus there is nothing rendered inside. `LazyGroup` solves this issue by
174 | rendering `noscript` only around specific wrapped elements (`img` and `iframe` by default). Also, further control is
175 | given with `childWrapper` component that will receive a set of props to make life easier.
176 |
177 | Use cases:
178 |
179 | 1. You want all contained images/iframes to be transitioned at the exact same time after everything is loaded.
180 | 2. You want to use the abstraction provided by `childWrapper` instead of writing custom logic.
181 | 3. You want to have slightly better performance by only checking the container element's location relative to the view.
182 |
183 |
184 | ### Usage
185 |
186 | ```jsx
187 | // curly brackets are required
188 | import { LazyGroup } from 'react-lazy'
189 |
190 | function ImageContainer({ childProps, children, isFailed, isLoaded, ...props }) {
191 | return (
192 | // usually the other props include `dangerouslySetInnerHtml` when rendering `noscript` element
193 |
194 | {isFailed ? 'The image did not load :( ' + childProps.src : children}
195 |
49 | `;
50 |
51 | exports[`LazyGroup childrenToWrap should wrap matching children in clientOnly mode 1`] = `
52 |
53 |
56 |
57 | Not wrapped! You can read me in the snapshot!
58 |
61 |
62 |
63 | `;
64 |
65 | exports[`LazyGroup ltIE9 should render just a div by default 1`] = ``;
66 |
67 | exports[`LazyGroup ltIE9 should render span inside div by default 1`] = `
68 |
69 |
70 | I am NOT inside IECC noscript!
71 |
72 |
73 | `;
74 |
75 | exports[`LazyGroup should let change component to be rendered 1`] = ``;
76 |
77 | exports[`LazyGroup should render just a div by default 1`] = ``;
78 |
79 | exports[`LazyGroup should render span inside div by default 1`] = `
80 |