├── .babelrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── config ├── webpack.config.base.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── example ├── index.html └── index.js ├── package.json ├── src ├── assets │ └── loading.png └── index.tsx ├── tea.yaml └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": "> 0.25%, not dead" 7 | } 8 | ], 9 | "@babel/preset-react" 10 | ], 11 | "plugins": [ 12 | "@babel/plugin-transform-runtime", 13 | "@babel/plugin-transform-modules-commonjs", 14 | [ 15 | "@babel/plugin-proposal-decorators", 16 | { 17 | "legacy": true 18 | } 19 | ], 20 | "@babel/plugin-proposal-class-properties", 21 | "@babel/plugin-proposal-object-rest-spread" 22 | ] 23 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | /dist 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - 8 7 | 8 | install: 9 | - npm install 10 | - npm install -g codecov 11 | 12 | before_script: 13 | - npm i -g npm 14 | 15 | script: 16 | - codecov 17 | 18 | after_success: 19 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jun 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | lazyImage 4 |

5 |

6 | GitHub package.json version 7 | npm 8 | 9 | npm bundle size 10 | 11 |

12 | 13 |

react-hook-lazy-image 图片懒加载

14 | 15 | 一个应用React Hooks基于IntersectionObserver API实现的图片懒加载组件,具有如下特点: 16 | 17 | - 简单好用,配置灵活 18 | - 兼容主流浏览器 19 | - 使用React Hooks实现 20 | - 使用TypeScript 21 | 22 | 23 | 24 | 25 | 26 | ## 安装 27 | ```jsx 28 | // 使用yarn安装 29 | yarn add react-hook-lazy-image 30 | 31 | // 使用npm安装 32 | npm install react-hook-lazy-image -S 33 | ``` 34 | 35 | ## 使用 36 | ```jsx 37 | import React from 'react'; 38 | import LazyImage from 'react-hook-lazy-image'; 39 | 40 | const list = [ // image src url ...]; 41 | 42 | const LazyLoad:React.FC = () => { 43 | const clientHeight = window.innerHeight; 44 | 45 | const style = {height: 300, width: 400, border: '1px solid #000', margin: '10px'}; 46 | 47 | return ( 48 | 55 | ) 56 | } 57 | 58 | export default LazyLoad; 59 | ``` 60 | 61 | 62 | ## API 63 | | 属性 | 类型 | 是否必填 | 默认值 | 描述 | 64 | | --- | --- | --- | --- | --- | 65 | | src | string | 否 | - | 图片的真实src,不传默认显示占位图 | 66 | | defaultSrc | string | 否 | LazyImage占位图 | 默认渲染的占位图src地址 | 67 | | style | object | 否 | {  width: 300,  height: 200 } | 图片样式 | 68 | | options | object | 否 | - | 自定义配置,通过配置可以指定图片与特定的父级元素交叉时才去加载真实图片,祥见[IntersectionObserver](https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver/IntersectionObserver) | 69 | 70 | 71 | 72 | ## 最后 73 | 如果觉得好用,请点个star支持一下哈~
74 | 如果在使用过程中有任何问题或者建议,可以在项目中提交issue,或者通过邮件与我取得联系,我会及时处理~
email:lujun5068@gmail.com 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /config/webpack.config.base.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | resolve: { 3 | extensions: ['.tsx', '.ts', '.js', '.jsx'] 4 | }, 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.jsx?$/, 9 | use: 'babel-loader', 10 | exclude: /node_modules/ 11 | }, 12 | { 13 | test: /\.tsx?$/, 14 | use: 'ts-loader', 15 | exclude: /node_modules/ 16 | }, 17 | { 18 | test: /\.(jpe?g|png|gif)/, 19 | use: { 20 | loader: 'file-loader', 21 | options: { 22 | name: 'images/[name][hash:8].[ext]' 23 | } 24 | } 25 | } 26 | ] 27 | } 28 | } -------------------------------------------------------------------------------- /config/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const merge = require('webpack-merge'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const baseConfig = require('./webpack.config.base'); 5 | 6 | const devConfig = { 7 | mode: 'development', 8 | entry: './example/index.js', 9 | output: { 10 | path: path.join(__dirname, '../example'), 11 | filename: '[name].js' 12 | }, 13 | devtool: 'source-map', 14 | plugins: [ 15 | new HtmlWebpackPlugin({ 16 | template: './example/index.html' 17 | }) 18 | ], 19 | devServer: { 20 | port: 4000, 21 | open: true, 22 | hot: true 23 | } 24 | } 25 | 26 | module.exports = merge(devConfig, baseConfig); -------------------------------------------------------------------------------- /config/webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const merge = require('webpack-merge'); 4 | const baseConfig = require('./webpack.config.base'); 5 | 6 | const prodConfig = { 7 | mode: 'production', 8 | entry: './src/index.tsx', 9 | output: { 10 | publicPath: '/', 11 | filename: 'index.js', 12 | path: path.resolve(__dirname, '../dist'), 13 | libraryTarget: 'umd', 14 | libraryExport: 'default', 15 | }, 16 | externals: { 17 | react: { 18 | root: "React", 19 | commonjs2: "react", 20 | commonjs: "react", 21 | amd: "react" 22 | }, 23 | "react-dom": { 24 | root: "ReactDOM", 25 | commonjs2: "react-dom", 26 | commonjs: "react-dom", 27 | amd: "react-dom" 28 | } 29 | }, 30 | plugins: [ 31 | new webpack.optimize.ModuleConcatenationPlugin(), 32 | ], 33 | } 34 | 35 | module.exports = merge(prodConfig, baseConfig); -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | lazy image 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import LazyImage from '../src/index'; 4 | 5 | const list = [ 6 | 'https://cdn.imgbin.com/1/6/10/imgbin-arctic-wolf-furry-fandom-carnivora-red-wolf-others-ZHnTC2tmQMPzs37cWQ7tPqp4R.jpg', 7 | 'https://cdn.imgbin.com/11/3/6/imgbin-london-soldier-icon-british-soldiers-two-royal-guards-illustration-DnXRqwwC7iWRUpyfneT3dqUpc.jpg', 8 | 'https://cdn.imgbin.com/17/18/0/imgbin-warrington-council-cheshire-east-london-borough-of-richmond-upon-thames-london-boroughs-others-hJL5XyKYPb4CjHKDUjJ1S9yWV.jpg', 9 | 'https://cdn.imgbin.com/21/6/22/imgbin-fermanagh-and-omagh-london-borough-of-lambeth-county-fermanagh-london-boroughs-others-LCDYu4VaxCS2kxqWHeaW4wgPf.jpg', 10 | 'https://cdn.imgbin.com/1/10/9/imgbin-london-borough-of-southwark-hayes-city-of-westminster-cities-of-london-and-westminster-geography-foreign-candidates-bwQZvxJYgVDGLtsWkkVrHgYY1.jpg', 11 | 'https://cdn.imgbin.com/12/10/17/imgbin-london-borough-of-hammersmith-and-fulham-silhouette-dog-yoga-sySpTkfUATdQ5A4xKzprWBTbG.jpg', 12 | 'https://cdn.imgbin.com/5/4/21/imgbin-london-borough-of-camden-hammersmith-commercial-building-facade-london-bridge-trading-bpv9mKugy0CnbgTak0Va2Wfy9.jpg', 13 | 'https://cdn.imgbin.com/17/2/24/imgbin-london-borough-of-ealing-london-boroughs-west-london-perceval-house-others-Qtw8cdkHXF7JUW9k7mezLELAR.jpg', 14 | 'https://cdn.imgbin.com/4/18/13/imgbin-kingston-upon-thames-royal-borough-of-kensington-and-chelsea-royal-borough-of-greenwich-london-borough-of-ealing-london-borough-of-hounslow-others-MvTGFyw0UDkbw6s6B5bWBTfZc.jpg', 15 | 'https://cdn.imgbin.com/25/22/16/imgbin-kingston-upon-thames-coat-of-arms-surrey-county-council-law-HA2zNQcxjxmMTM0FjHL4ZrsbD.jpg', 16 | 'https://cdn.imgbin.com/2/15/11/imgbin-county-armagh-counties-of-ireland-republic-of-ireland-coat-of-arms-of-ireland-creative-harp-fvtXjkya9NkurELWVaZndG6jL.jpg', 17 | 'https://cdn.imgbin.com/10/9/18/imgbin-wetsuit-yulex-counties-of-ireland-parthenium-argentatum-county-donegal-surfer-AUZFTFWmktMjJPSa05ZRJfcmB.jpg', 18 | 'https://cdn.imgbin.com/22/16/18/imgbin-counties-of-the-kingdom-of-hungary-zalaegerszeg-list-of-regions-of-hungary-town-with-county-rights-hungarian-Lzu3F4V6eSi0KDQWt53ydV9Fp.jpg', 19 | 'https://cdn.imgbin.com/22/9/1/imgbin-fifa-online-3-need-for-speed-edge-mabinogi-vindictus-nexus-the-kingdom-of-the-winds-festivals-cy4Q8uiTF95ihz6ZBjyCN10tZ.jpg', 20 | 'https://cdn.imgbin.com/25/8/24/imgbin-need-for-speed-shift-need-for-speed-most-wanted-shift-2-unleashed-xbox-360-need-for-speed-hot-pursuit-need-for-speed-8RjDqSUsEzgJFskmbVAR6QjWa.jpg', 21 | 'https://cdn.imgbin.com/23/20/11/imgbin-need-for-speed-hot-pursuit-2-need-for-speed-iii-hot-pursuit-need-for-speed-most-wanted-xbox-360-need-for-speed-69VBuefWmKD6c4P2d1hC2Hv9m.jpg', 22 | ] 23 | 24 | 25 | 26 | const LazyLoad = () => { 27 | const clientHeight = window.innerHeight; 28 | 29 | const style = {height: 300, width: 400, border: '1px solid #000', margin: '10px auto'}; 30 | 31 | return ( 32 | 39 | ) 40 | } 41 | 42 | render( , document.getElementById('root')); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-hook-lazy-image", 3 | "version": "1.1.1", 4 | "description": "A lazy loading image component that applies React Hooks based on IntersectionObserver API", 5 | "main": "dist/index.js", 6 | "files": [ 7 | "dist" 8 | ], 9 | "scripts": { 10 | "build": "rm -rf dist && webpack --config ./config/webpack.config.prod.js", 11 | "pub": "npm run build && npm publish", 12 | "dev": "webpack-dev-server --config ./config/webpack.config.dev.js", 13 | "report-coverage": "codecov" 14 | }, 15 | "repository": "https://github.com/lujun5068/lazy-image", 16 | "keywords": [ 17 | "lazy", 18 | "lazy-image", 19 | "lazy-load-image", 20 | "load-image", 21 | "image", 22 | "懒加载", 23 | "图片懒加载" 24 | ], 25 | "author": "lujun_smile", 26 | "license": "MIT", 27 | "dependencies": { 28 | "react": "^16.12.0", 29 | "react-dom": "^16.12.0" 30 | }, 31 | "devDependencies": { 32 | "@babel/cli": "^7.7.0", 33 | "@babel/core": "^7.7.2", 34 | "@babel/plugin-proposal-class-properties": "^7.0.0", 35 | "@babel/plugin-proposal-decorators": "^7.0.0", 36 | "@babel/plugin-transform-modules-commonjs": "^7.0.0", 37 | "@babel/plugin-transform-runtime": "^7.0.0", 38 | "@babel/preset-env": "^7.0.0", 39 | "@babel/preset-react": "^7.0.0", 40 | "@babel/runtime": "^7.7.4", 41 | "@types/node": "^12.12.8", 42 | "@types/react": "^16.9.11", 43 | "@types/react-dom": "^16.9.4", 44 | "babel-loader": "^8.0.6", 45 | "file-loader": "^4.2.0", 46 | "html-webpack-plugin": "next", 47 | "ts-loader": "^6.2.1", 48 | "typescript": "^3.7.2", 49 | "webpack": "^4.41.2", 50 | "webpack-cli": "^3.3.10", 51 | "webpack-dev-server": "^3.1.5", 52 | "webpack-merge": "^4.1.4" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/assets/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lujun5068/lazy-image/c885d859337563129fde04f72c958dd99cc3e7de/src/assets/loading.png -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useRef} from 'react'; 2 | interface LazyImageProps { 3 | /** 4 | * 图片的真实src,不传默认显示占位图 5 | */ 6 | src?: string; 7 | 8 | /** 9 | * 默认的占位图片,可以自己传,默认使用lazyImage的占位图 10 | */ 11 | defaultSrc?: string; 12 | 13 | /** 14 | * 图片样式 15 | */ 16 | style?: React.CSSProperties; 17 | 18 | /** 19 | * 自定义配置项 20 | */ 21 | options?: IntersectionObserver 22 | } 23 | 24 | const defaultUrl = 'data:image/png;base64,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'; 25 | 26 | const defaultImgStyle:React.CSSProperties = { 27 | width: 300, 28 | height: 200 29 | } 30 | 31 | const LazyImage:React.FC = ({src= defaultUrl, style=defaultImgStyle, defaultSrc= defaultUrl, options={}}) => { 32 | const imgRef = useRef(null); 33 | 34 | const setDefaultSrc = () => { 35 | imgRef.current.src = defaultSrc; 36 | } 37 | 38 | const proxyImage = () => { 39 | const img = new Image(); 40 | img.onload = () => { 41 | imgRef.current.src = src; 42 | } 43 | return { 44 | setSrc() { 45 | img.src = src; 46 | } 47 | } 48 | } 49 | 50 | 51 | useEffect(() => { 52 | setDefaultSrc(); 53 | 54 | const observer = new IntersectionObserver((entries) => { 55 | entries.forEach(item => { 56 | if (item.isIntersecting) { 57 | proxyImage().setSrc(); 58 | if (src) { 59 | observer.unobserve(item.target); 60 | } 61 | } 62 | }) 63 | }, {...options}) 64 | 65 | observer.observe(imgRef.current); 66 | 67 | return () => { 68 | observer.unobserve(imgRef.current); 69 | } 70 | 71 | }, []) 72 | 73 | return ( 74 |
75 | 76 |
77 | ) 78 | } 79 | 80 | export default LazyImage; -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | # https://tea.xyz/what-is-this-file 2 | --- 3 | version: 1.0.0 4 | codeOwners: 5 | - '0xee090f2d1Ab50b0115DE74600390e018ee895291' 6 | quorum: 1 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | { 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "target": "es5", 6 | "jsx": "react", 7 | "lib": ["dom", "esnext"], 8 | "module": "esnext", 9 | "sourceMap": true, 10 | "allowSyntheticDefaultImports": true, 11 | "moduleResolution": "node", 12 | "rootDir": "src", 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "suppressImplicitAnyIndexErrors": true, 16 | "noUnusedLocals": true, 17 | "experimentalDecorators": true, 18 | "declaration": true 19 | }, 20 | "exclude": [ 21 | "example", 22 | "node_modules", 23 | "build", 24 | "dist", 25 | "lib", 26 | "tests" 27 | ] 28 | } 29 | 30 | --------------------------------------------------------------------------------