mirror of
https://github.com/Aviortheking/next-template.git
synced 2025-06-07 20:29:53 +00:00
Added Image Component
Signed-off-by: Avior <florian.bouillon@delta-wings.net>
This commit is contained in:
parent
f5cdda822c
commit
e37dc1a178
41
components/Image.module.styl
Normal file
41
components/Image.module.styl
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
.image
|
||||||
|
transition .3s
|
||||||
|
object-fit contain
|
||||||
|
z-index 2
|
||||||
|
transition .3s
|
||||||
|
|
||||||
|
|
||||||
|
&.ph1
|
||||||
|
position fixed
|
||||||
|
transition 0s
|
||||||
|
|
||||||
|
|
||||||
|
&.ph2
|
||||||
|
width 100% !important
|
||||||
|
position fixed
|
||||||
|
max-width 100% !important
|
||||||
|
max-height 100% !important
|
||||||
|
box-sizing border-box
|
||||||
|
padding 5%
|
||||||
|
top 0 !important
|
||||||
|
left 0 !important
|
||||||
|
height 100%
|
||||||
|
background #000000A0
|
||||||
|
|
||||||
|
|
||||||
|
&.after
|
||||||
|
background #00000000
|
||||||
|
/* height 100% */
|
||||||
|
box-sizing border-box
|
||||||
|
position fixed
|
||||||
|
/* width 100% */
|
||||||
|
z-index 0
|
||||||
|
padding initial
|
||||||
|
|
||||||
|
|
||||||
|
.hideOverflow
|
||||||
|
overflow hidden
|
||||||
|
|
||||||
|
|
||||||
|
.none
|
||||||
|
display none
|
209
components/Image.tsx
Normal file
209
components/Image.tsx
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ import React from 'react'
|
|||||||
|
|
||||||
import '../styl/index.styl'
|
import '../styl/index.styl'
|
||||||
|
|
||||||
export default class Index extends App {
|
export default class CApp extends App {
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { Component, pageProps } = this.props
|
const { Component, pageProps } = this.props
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import '../styl/index.styl'
|
import '../styl/index.styl'
|
||||||
|
import HelloWorld from '../components/HelloWorld'
|
||||||
|
import Image from '../components/Image'
|
||||||
|
|
||||||
export default class Index extends React.Component {
|
export default class Index extends React.Component {
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return(
|
return(
|
||||||
<>
|
<>
|
||||||
<h1>Hello World !</h1>
|
<HelloWorld>Hello World</HelloWorld>
|
||||||
<style jsx>{`
|
<Image
|
||||||
h1
|
max={{height:400,width:200}}
|
||||||
font-size: 39px
|
default={{width:200,height:100}}
|
||||||
`}</style>
|
deleteOnError={true}
|
||||||
|
src="https://source.unusplash.com/random/800x600"
|
||||||
|
/>
|
||||||
|
<HelloWorld>Hello World</HelloWorld>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user