一个高效、灵活的图片 React
组件,自动实现图片懒加载功能,并能按需适配云厂商的图片处理功能。
npm
安装:
~ npm install @robot-img/react-img
如果你使用的是 Emotion 或是 styled-components ,推荐使用以下配置方式:
import React from 'react'
import ReactDOM from 'react-dom'
import styled from '@emotion/styled'
import {
checkWebpSupportedSync,
createSrcTplOfAliOss,
imgPool,
ImgProps,
useImg,
} from '@robot-img/react-img'
// 设置图片组件样式
const StyledImg = styled.div<{ $src: string }>`
background-size: cover;
background-repeat: no-repeat;
background-position: center;
transition: background-image 0.3s;
${(props) => props.$src && `background-image: url(${props.$src});`}
`
// 使用 useImg 自定义组件
const Img = React.forwardRef<HTMLDivElement, ImgProps<HTMLDivElement>>((props, ref) => {
const { domProps, state, handleRef } = useImg(props, ref)
return <StyledImg {...domProps} $src={state.src} ref={handleRef} />
})
// 具体使用时,设置对应的样式
const OssImg = styled(Img)`
width: 200px;
height: 160px;
`
function main() {
// 根据云厂商来设置全局图片后缀,获取最适合的图片
// 这里使用的阿里云的图片处理作为案例
imgPool.reset({
createSrcTpl: createSrcTplOfAliOss({
// 判断浏览器是否支持 webp 格式图片
webp: checkWebpSupportedSync(),
}),
})
const imgSrc = '//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg'
// 真实加载的图片为: ${imgSrc}?x-oss-process=image/resize,m_fill,w_400,h_320/format,webp
ReactDOM.render(<OssImg src={imgSrc} />, document.getElementById('10-recommend'))
}
main()
默认情况将只实现懒加载功能,而不对图片后缀做任何处理,这个时候无需做任何设置,可以直接使用:
import React from 'react'
import ReactDOM from 'react-dom'
import { Img } from '@robot-img/react-img'
function main() {
const imgSrc = '//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg'
// 图片后缀不做任何处理
ReactDOM.render(
<Img src={imgSrc} style={{ width: 200 }} />,
document.getElementById('20-default')
)
}
main()
当然,你也可以使用 css-module
或者 less\scss
等方案自行设置全局默认样式
import React from 'react'
import ReactDOM from 'react-dom'
import styled from '@emotion/styled'
import {
checkWebpSupported,
createImgPool,
createSrcTplOfAliOss,
Img,
ImgPoolContext,
} from '@robot-img/react-img'
const Container = styled.div`
.div {
width: 300px;
height: 240px;
display: flex;
align-items: center;
justify-content: center;
}
.content {
width: 160px;
height: 100px;
background-color: rgba(255, 255, 255, 0.5);
text-align: center;
line-height: 80px;
padding: 10px;
}
.span {
width: 50px;
height: 30px;
margin-right: 10px;
}
.img {
width: 50px;
height: 30px;
}
`
async function main() {
// 判断浏览器是否支持 webp 格式图片
const webp = await checkWebpSupported()
// 还是以阿里云为例
const imgPool = createImgPool({
createSrcTpl: createSrcTplOfAliOss({
webp,
}),
globalVars: {
className: 'css-style-img',
},
})
// 可以用这个方法加一个默认样式
/**
* 根据 globalVars.className 设置一个全局默认样式
* 默认样式为:
* ```
* .${globalVars.className} {
* transition: background-image .3s;
* background-size: cover;
* background-position: center;
* background-repeat: no-repeat;
* }
* span.${globalVars.className} {
* display: inline-block;
* }
* ```
*/
imgPool.appendDefaultStyle()
const imgSrc = '//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg'
// 真实加载的图片为: ${imgSrc}?x-oss-process=image/resize,m_fill,w_400,h_320/format,webp
ReactDOM.render(
<Container>
<ImgPoolContext.Provider value={imgPool}>
<Img.Div src={imgSrc} className="div">
<div className="content">
<Img.Span src={imgSrc} className="span" />
<Img src={imgSrc} className="img" />
</div>
</Img.Div>
</ImgPoolContext.Provider>
</Container>,
document.getElementById('30-css')
)
}
main()
使用不同云厂商的图片处理功能按需使用图片,已有图片处理函数参考:
也可以通过
createSrcTplFactory
快速创建一个全局图片处理函数
import React from 'react'
import ReactDOM from 'react-dom'
import styled from '@emotion/styled'
import {
checkWebpSupportedSync,
createImgPool,
createSrcTplFactory,
createSrcTplOfAliOss,
createSrcTplOfKSYunKS3,
createSrcTplOfTencent,
Img,
ImgPoolContext,
} from '@robot-img/react-img'
const Container = styled.div`
.cloud-img {
width: 100px;
height: 80px;
margin-bottom: 10px;
margin-right: 10px;
}
`
function main() {
// 判断浏览器是否支持 webp 格式图片
const webp = checkWebpSupportedSync()
// 阿里云,参考:https://help.aliyun.com/document_detail/44688.html
const imgPoolAliOss = createImgPool({
createSrcTpl: createSrcTplOfAliOss({
webp,
}),
globalVars: {
className: 'cloud-img',
},
})
// 金山云,详见:https://docs.ksyun.com/documents/886
const imgPoolK3S = createImgPool({
createSrcTpl: createSrcTplOfKSYunKS3({
webp,
}),
globalVars: {
className: 'cloud-img',
},
})
// 腾讯云,详见:https://cloud.tencent.com/document/product/460/36541
const imgPoolTencent = createImgPool({
createSrcTpl: createSrcTplOfTencent({
webp,
}),
globalVars: {
className: 'cloud-img',
},
})
// 自定义,以腾讯云为基础,比如自定义 webp 格式的质量
const createCustomSrcTpl = createSrcTplFactory((globalVars) => ({ rect, src }) => {
const configs: string[] = []
if (rect.width && rect.height) {
const w = Math.floor(globalVars.ratio * rect.width)
const h = Math.floor(globalVars.ratio * rect.height)
configs.push(`1/w/${w}/h/${h}`)
}
if (globalVars.webp) {
configs.push('format/webp/quality/90')
}
if (configs.length < 1) {
return src
}
return `${src}?imageView2/${configs.join('/')}`
})
const imgPoolCustom = createImgPool({
createSrcTpl: createCustomSrcTpl({
webp,
}),
globalVars: {
className: 'cloud-img',
},
})
ReactDOM.render(
<Container>
<ImgPoolContext.Provider value={imgPoolAliOss}>
<Img src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg" />
</ImgPoolContext.Provider>
<ImgPoolContext.Provider value={imgPoolK3S}>
<Img src="//ks3-cn-beijing.ksyun.com/ks3-resources/suiyi.jpg" />
</ImgPoolContext.Provider>
<ImgPoolContext.Provider value={imgPoolTencent}>
<Img src="//examples-1251000004.cos.ap-shanghai.myqcloud.com/sample.jpeg" />
</ImgPoolContext.Provider>
<ImgPoolContext.Provider value={imgPoolCustom}>
<Img src="//examples-1251000004.cos.ap-shanghai.myqcloud.com/sample.jpeg" />
</ImgPoolContext.Provider>
</Container>,
document.getElementById('40-cloud')
)
}
main()
云厂商可用的图片处理不仅仅只有缩放,也可以做一些特殊处理,比如缩放模式、高斯模糊。
import React from 'react'
import ReactDOM from 'react-dom'
import styled from '@emotion/styled'
import {
checkWebpSupportedSync,
createImgPool,
createSrcTplOfAliOss,
Img,
ImgPoolContext,
} from '@robot-img/react-img'
const Container = styled.div`
.ali-oss-img {
width: 200px;
height: 160px;
background-size: contain;
background-color: #ccc;
background-repeat: no-repeat;
background-position: center;
transition: background-image 0.3s;
}
`
function main() {
// 阿里云,参考:https://help.aliyun.com/document_detail/44688.html
const imgPoolAliOss = createImgPool({
createSrcTpl: createSrcTplOfAliOss({
webp: checkWebpSupportedSync(),
}),
globalVars: {
className: 'ali-oss-img',
},
})
ReactDOM.render(
<Container>
<ImgPoolContext.Provider value={imgPoolAliOss}>
<Img.Div
src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg"
srcTpl={({ src, ratioWidth, ratioHeight, webp }) =>
`${src}?x-oss-process=image/resize,m_lfit,w_${ratioWidth},h_${ratioHeight}${
webp ? '/format,webp' : ''
}/blur,r_3,s_2`
}
/>
</ImgPoolContext.Provider>
</Container>,
document.getElementById('45-tpl')
)
}
main()
使用 ImgContainer
组件自定义图片容器
import React from 'react'
import ReactDOM from 'react-dom'
import styled from '@emotion/styled'
import { Img, ImgContainer, imgPool } from '@robot-img/react-img'
import { checkWebpSupportedSync, createSrcTplOfAliOss } from '@robot-img/utils'
const Container = styled.div`
.img-container {
height: 300px;
overflow: scroll;
}
.img {
width: 200px;
height: 160px;
background-size: cover;
margin-bottom: 10px;
}
`
async function main() {
const webp = checkWebpSupportedSync()
imgPool.reset({
createSrcTpl: createSrcTplOfAliOss({
webp,
}),
globalVars: {
className: 'robot-img',
},
})
ReactDOM.render(
<Container>
<ImgContainer
className="img-container"
createSrcTpl={createSrcTplOfAliOss({
webp,
})}
globalVars={{ className: 'robot-img' }}
>
<Img.Div className="img" src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg" />
<Img.Div className="img" src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg" />
<Img.Div className="img" src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg" />
<Img.Div className="img" src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg" />
<Img.Div className="img" src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg" />
<Img.Div className="img" src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg" />
<Img.Div className="img" src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg" />
<Img.Div className="img" src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg" />
<Img.Div className="img" src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg" />
<Img.Div className="img" src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg" />
<Img.Div className="img" src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg" />
<Img.Div className="img" src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg" />
<Img.Div className="img" src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg" />
<Img.Div className="img" src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg" />
<Img.Div className="img" src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg" />
</ImgContainer>
</Container>,
document.getElementById('50-container')
)
}
main()
图片组件会根据图片的面积自动适应,默认的判断方法:
function defaultShouldUpdate(newRect: DOMRect, oldRect: DOMRect) {
// 当 width or height 变大 20% 时,才更新图片
return newRect.width > oldRect.width * 1.2 || newRect.height > oldRect.height * 1.2
}
也可以通过给组件传 shouldUpdate
来定义图片是否需要更新,也可以通过
imgPool.reset({
globalVars: { shouldUpdate: (newRect, oldRect) => true }
})
设置全家默认的 shouldUpdate
。
当前的 src: $
import React from 'react'
import ReactDOM from 'react-dom'
import styled from '@emotion/styled'
import {
checkWebpSupportedSync,
createSrcTplOfAliOss,
imgPool,
ImgProps,
useImg,
} from '@robot-img/react-img'
const Container = styled.div`
.robot-img {
background-size: cover;
background-position: center;
transition: background-image 0.3s;
margin: 20px 0;
}
button {
margin-right: 10px;
user-select: none;
}
`
const StyledImg = styled.div<{ $src: string }>`
background-size: cover;
background-repeat: no-repeat;
background-position: center;
transition: background-image 0.3s;
${(props) => props.$src && `background-image: url(${props.$src});`}
`
// 使用 useImg 自定义组件
const Img = React.forwardRef<HTMLDivElement, ImgProps<HTMLDivElement>>((props, ref) => {
const { domProps, state, handleRef } = useImg(props, ref)
return (
<div>
<StyledImg {...domProps} $src={state.src} ref={handleRef} />
<p>当前的 src: ${state.src}</p>
</div>
)
})
function App() {
const [ratio, setRatio] = React.useState(1)
const handleAdd = React.useCallback(() => {
setRatio((oldRatio) => Math.min(3, oldRatio * 1.05))
}, [])
const handleCut = React.useCallback(() => {
setRatio((oldRatio) => oldRatio * 0.95)
}, [])
const imgStyle = React.useMemo(
() => ({
width: 100 * ratio,
height: 60 * ratio,
}),
[ratio]
)
return (
<Container>
<button onClick={handleAdd}>宽高变大10%</button>
<button onClick={handleCut}>宽高变小10%</button>
<Img
style={imgStyle}
lazy="resize"
src="//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg"
/>
</Container>
)
}
function main() {
const webp = checkWebpSupportedSync()
imgPool.reset({
createSrcTpl: createSrcTplOfAliOss({
webp,
}),
globalVars: {
className: 'robot-img',
},
})
ReactDOM.render(<App />, document.getElementById('60-resize'))
}
main()
图片组件的属性 defaultSrc
、 errorSrc
、 statusClassNamePrefix
、 loadingType
都可以通过 imgPool.globalVars
来设置。
注意:
defaultSrc
、errorSrc
不会进行图片处理功能
import React from 'react'
import ReactDOM from 'react-dom'
import styled from '@emotion/styled'
import {
checkWebpSupportedSync,
createImgPool,
createSrcTplOfAliOss,
Img,
ImgPoolContext,
waitImgLoaded,
} from '@robot-img/react-img'
const Container = styled.div`
.ali-oss-img {
width: 200px;
height: 160px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
transition: background-image 0.3s;
border: 1px solid #ccc;
&-loading {
border-color: blue;
background-size: 64px 64px;
}
&-loaded {
border-color: green;
}
&-error {
border-color: red;
background-size: 64px 64px;
}
}
button {
margin-top: 10px;
margin-right: 10px;
user-select: none;
}
`
function Demo() {
const resolve = React.useRef<(args?: any) => void>(() => {})
const reject = React.useRef<(args?: any) => void>(() => {})
const wait = React.useCallback(
() =>
new Promise((r, rj) => {
resolve.current = r
reject.current = rj
}),
[]
)
const prepareImg: typeof waitImgLoaded = React.useMemo(() => {
return async (imgSrc, crossOrigin) => {
const img = await waitImgLoaded(imgSrc, crossOrigin)
await wait()
return img
}
}, [wait])
const handleResolve = React.useCallback(() => {
resolve.current()
}, [])
const handleReject = React.useCallback(() => {
reject.current()
}, [])
const [src, setSrc] = React.useState('')
const handleSrc = React.useCallback(() => {
setSrc('//image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg')
}, [])
const handleClear = React.useCallback(() => {
setSrc('')
}, [])
return (
<div>
<Img.Div src={src} prepareImg={prepareImg} />
<button onClick={src ? handleClear : handleSrc}>{src ? 'Clear img' : 'Load img'}</button>
<button onClick={handleResolve}>Mock success</button>
<button onClick={handleReject}>Mock reject</button>
</div>
)
}
async function main() {
// 阿里云,参考:https://help.aliyun.com/document_detail/44688.html
const imgPoolAliOss = createImgPool({
createSrcTpl: createSrcTplOfAliOss({
webp: checkWebpSupportedSync(),
}),
globalVars: {
className: 'ali-oss-img',
statusClassNamePrefix: 'ali-oss-img-',
defaultSrc: './imgs/picture.png',
errorSrc: './imgs/error.png',
loadingType: 'src',
},
})
ReactDOM.render(
<Container>
<ImgPoolContext.Provider value={imgPoolAliOss}>
<Demo />
</ImgPoolContext.Provider>
</Container>,
document.getElementById('70-globals')
)
}
main()