mirror of
https://github.com/Aviortheking/next-template.git
synced 2025-04-22 10:42:10 +00:00
210 lines
5.3 KiB
TypeScript
210 lines
5.3 KiB
TypeScript
import React, { SyntheticEvent } from 'react'
|
|
import css from './Image.module.styl'
|
|
|
|
interface Props {
|
|
defaultHeight?: number
|
|
src?: string
|
|
sources?: Array<string>
|
|
deleteOnError?: boolean
|
|
downgradeOnError?: string
|
|
canFullscreen?: boolean
|
|
max?: {
|
|
height?: number|string
|
|
width?: number|string
|
|
}
|
|
width?: number|string
|
|
default?: {
|
|
height?: number|string
|
|
width?: number|string
|
|
}
|
|
alt?: string
|
|
}
|
|
|
|
const mimeTypes = {
|
|
apng: 'image/apng',
|
|
bmp: 'image/bmp',
|
|
gif: 'image/gif',
|
|
|
|
ico: 'image/x-icon',
|
|
cur: 'image/x-icon',
|
|
|
|
jpg: 'image/jpeg',
|
|
jpeg: 'image/jpeg',
|
|
jfif: 'image/jpeg',
|
|
pjpeg: 'image/jpeg',
|
|
pjp: 'image/jpeg',
|
|
|
|
png: 'image/png',
|
|
svg: 'image/svg+xml',
|
|
|
|
tif: 'image/tiff',
|
|
tiff: 'image/tiff',
|
|
|
|
webp: 'image/webp',
|
|
}
|
|
|
|
const getMimeType = (img: string) => {
|
|
const arr = img.split('.')
|
|
return mimeTypes[arr[arr.length-1] as 'apng'] || mimeTypes.png
|
|
}
|
|
|
|
type evType<T = HTMLImageElement> = SyntheticEvent<T, Event>
|
|
|
|
export default class Image extends React.Component<Props, {}> {
|
|
|
|
private ref: React.RefObject<HTMLImageElement> = React.createRef()
|
|
private plchldr: React.RefObject<HTMLDivElement> = React.createRef()
|
|
private parent: React.RefObject<HTMLDivElement> = React.createRef()
|
|
private pic: React.RefObject<HTMLPictureElement> = React.createRef()
|
|
|
|
private wasDowngraded = false
|
|
private cardPos: Array<number> = []
|
|
private cardSize: Array<number> = []
|
|
|
|
private isFullscreen = false
|
|
|
|
public async componentDidMount() {
|
|
if (this.props.canFullscreen) {
|
|
window.addEventListener('scroll', this.onScroll)
|
|
window.addEventListener('resize', this.onResize)
|
|
this.onScroll()
|
|
this.onResize()
|
|
}
|
|
}
|
|
|
|
public async componentDidUpdate() {
|
|
this.pic.current?.classList.remove(css.none)
|
|
if (this.props.canFullscreen) {
|
|
this.onScroll()
|
|
this.onResize()
|
|
}
|
|
if (this.isFullscreen) {this.onClick()}
|
|
}
|
|
|
|
public async componentWillUnmount() {
|
|
if (this.props.canFullscreen) {
|
|
window.removeEventListener('scroll', this.onScroll)
|
|
window.removeEventListener('resize', this.onResize)
|
|
}
|
|
}
|
|
|
|
public render() {
|
|
const pic = (
|
|
<picture ref={this.pic}>
|
|
{this.props.sources && this.props.sources.map((el,index) => (
|
|
<source key={index} srcSet={el} type={getMimeType(el)}/>
|
|
))}
|
|
<img
|
|
className={css.image}
|
|
ref={this.ref}
|
|
src={this.props.src}
|
|
onClick={this.props.canFullscreen && this.onClick || undefined}
|
|
onLoad={this.props.default && this.onLoad || undefined}
|
|
onError={this.props.deleteOnError && this.onError || undefined}
|
|
style={{
|
|
width: this.props.default?.width,
|
|
height: this.props.default?.height,
|
|
maxHeight: this.props.max?.height,
|
|
maxWidth: this.props.max?.width,
|
|
}}
|
|
alt={this.props.alt}
|
|
/>
|
|
</picture>
|
|
)
|
|
if (this.props.canFullscreen) {return (
|
|
<div ref={this.parent}>
|
|
<div ref={this.plchldr} className={css.none}></div>
|
|
{pic}
|
|
</div>
|
|
)}
|
|
return pic
|
|
}
|
|
|
|
private onScroll = async () => {
|
|
if (!this.ref.current || this.isFullscreen || !this.props.canFullscreen) {
|
|
return
|
|
}
|
|
|
|
this.cardPos = [this.ref.current.offsetTop - window.scrollY, this.ref.current.offsetLeft - window.scrollX]
|
|
this.ref.current.style.top = this.cardPos[0] + 'px'
|
|
this.ref.current.style.left = this.cardPos[1] + 'px'
|
|
}
|
|
|
|
private onResize = async () => {
|
|
if (!this.ref.current || !this.plchldr.current || !this.props.canFullscreen || this.isFullscreen) {
|
|
return
|
|
}
|
|
let tmp = [this.ref.current.offsetHeight, this.ref.current.offsetWidth]
|
|
if (this.parent.current) {
|
|
tmp = [this.parent.current.offsetHeight, this.ref.current.offsetWidth]
|
|
}
|
|
this.plchldr.current.style.width = `${tmp[1]}px`
|
|
this.plchldr.current.style.height = `${tmp[0]}px`
|
|
}
|
|
|
|
private onClick = async () => {
|
|
if (!this.ref.current || !this.props.canFullscreen || !this.plchldr.current) {
|
|
return
|
|
}
|
|
|
|
const i = this.ref.current
|
|
const c = this.plchldr.current
|
|
const body = document.body
|
|
i.style.top = this.cardPos[0] + 'px'
|
|
i.style.left = this.cardPos[1] + 'px'
|
|
|
|
if (this.isFullscreen) {
|
|
i.style.width = this.cardSize[1] + 'px'
|
|
i.style.height = this.cardSize[0] + 'px'
|
|
body.classList.remove(css.hideOverflow)
|
|
i.classList.remove(css.ph2)
|
|
i.classList.add(css.after)
|
|
|
|
setTimeout(() => {
|
|
if (i.classList.contains(css.ph2) || i.classList.contains(css.ph1) || this.isFullscreen) {return}
|
|
const w = this.valToPixel(this.props.width)
|
|
const mh = this.valToPixel(this.props.max?.height)
|
|
const mw = this.valToPixel(this.props.max?.width)
|
|
c.classList.add(css.none)
|
|
i.style.height = ''
|
|
i.style.width = w
|
|
i.style.maxHeight = mh
|
|
i.style.maxWidth = mw
|
|
i.classList.remove(css.after)
|
|
}, 350)
|
|
this.isFullscreen = false
|
|
} else {
|
|
i.classList.add(css.ph1)
|
|
c.classList.remove(css.none)
|
|
i.classList.add(css.ph2)
|
|
i.classList.remove(css.ph1)
|
|
body.classList.add(css.hideOverflow)
|
|
this.isFullscreen = true
|
|
}
|
|
}
|
|
|
|
private valToPixel(value: number|string|undefined): string {
|
|
if (typeof value === 'number') {
|
|
return `${value}px`
|
|
}
|
|
if (typeof value === 'undefined') {
|
|
return ''
|
|
}
|
|
return value
|
|
}
|
|
|
|
private onLoad = async (ev: evType) => {
|
|
ev.currentTarget.style.height = ''
|
|
ev.currentTarget.style.width = ''
|
|
}
|
|
|
|
private onError = async (ev: evType) => {
|
|
this.w('Picture not loaded', ev.currentTarget.src)
|
|
ev.currentTarget.parentElement?.classList.add(css.none)
|
|
}
|
|
|
|
private w(...messages: any) {
|
|
console.warn('[ Picture ]', ...messages)
|
|
}
|
|
}
|