我几天前刚刚完成了这项工作。但尚未缩短、优化和添加视频支持
这是我的代码:
import React, { useEffect, useState } from 'react';
import "./MediaViewer.css";
import { useGlobalObj, globalObj } from 'logic/zergski-global-object';
// content JSON
import { mediaJSON } from 'logic/content-collections';
// components
import Anchor from 'shared/Anchor';
let current = 'can1';
let canvas = {
can1: {
pos: 'center',
trans: true,
source: mediaJSON[0].src,
},
can2: {
pos: 'right',
trans: true,
source: mediaJSON[1].src,
},
can3: {
pos: 'left',
trans: false,
source: mediaJSON[mediaJSON.length-1].src,
}
}
// iterator
// TODO! add settings
const iterator = ( array, setState, initIndex ) => {
let index = initIndex;
let count = array.length - 1; // content count
let nextSource = index+2; // next imagesource to load
let nextCanvas = 0; // canvas position
let nextImageSource = nextCanvas - 1;
return {
next() {
index = index === count ? 0 : ++index;
nextSource = index+1 > count ? 0 : index+1;
Object.entries(canvas).forEach( c => {
canvas[c[0]].pos = c[1].pos === 'center' ? 'left' : c[1].pos === 'right' ? 'center' : 'right';
c[1].pos === 'center' && (current = c[0]);
if ( c[1].pos === 'right' ) {
canvas[c[0]].trans = false;
canvas[c[0]].source = mediaJSON[nextSource].src;
} else {
canvas[c[0]].trans = true;
}
});
setState({
...canvas,
counter: index+1,
});
},
prev() {
index = index === 0 ? count : --index;
nextSource = index-1 < 0 ? count : index-1;
Object.entries(canvas).forEach( c => {
canvas[c[0]].pos = c[1].pos === 'center' ? 'right' : c[1].pos === 'left' ? 'center' : 'left';
c[1].pos === 'center' && (current = c[0]);
if ( c[1].pos === 'left' ) {
canvas[c[0]].trans = false;
canvas[c[0]].source = mediaJSON[nextSource].src;
} else {
canvas[c[0]].trans = true;
}
});
setState({
...canvas,
counter: index+1,
});
}
}
}
var lastTouch = 0;
var content = Object.create(null);
// component for viewing images, videos and documents
const MediaViewer = props => {
const mediaViewer = {
display: 'hidden',
contentIndex: 0,
initialState: { display: 'hidden' },
}
const [ viewerState, setViewerState ] = useGlobalObj({ mediaViewer });
const [ state, setState ] = useState({
...canvas,
counter: 1,
});
const [ touchState, setTouchState ] = useState({
touch: false,
movement: 0,
});
if ( viewerState.display === 'show' ) {
document.body.classList.add('scroll-lock');
setTimeout(()=>{
globalObj.mediaViewer.setState({ display: 'visible' });
}, 50)
}
useEffect(() => {
const { display, index } = viewerState;
if ( display === 'show' ) {
content = iterator( mediaJSON, setState, index );
current = 'can1';
canvas = {
can1: {
pos: 'center',
trans: false,
source: mediaJSON[index].src,
},
can2: {
pos: 'right',
trans: false,
source: mediaJSON[index === mediaJSON.length-1 ? 0 : index+1].src,
},
can3: {
pos: 'left',
trans: false,
source: mediaJSON[index === 0 ? mediaJSON.length-1 : index-1].src,
}
}
setState({
...canvas,
counter: index+1,
})
}
}, [viewerState]);
const handleClose = () => {
globalObj.mediaViewer.setState({ display: 'hide' });
setTimeout(()=>{
globalObj.mediaViewer.setState({ display: 'hidden' });
}, 400)
document.body.classList.remove('scroll-lock');
}
// input handlers
const handleMovement = ( delta, touch=false ) => {
let nextMovement = touchState.movement - delta;
canvas[current].trans = false;
canvas[current].movement = nextMovement;
setTouchState({ movement: nextMovement });
}
// touch
const handleTouchStart = (e) => {
lastTouch = e.nativeEvent.touches[0].clientX;
}
const handleTouchMove = (e) => {
const delta = lastTouch - e.nativeEvent.touches[0].clientX;
lastTouch = e.nativeEvent.touches[0].clientX;
handleMovement(Math.floor(delta), true);
}
const handleTouchEnd = (e) => {
touchState.movement < -50 ? content.next() : touchState.movement > 50 && content.prev();
lastTouch = 0;
canvas[current].trans = true;
setTouchState({ movement: 0 });
}
return (
<div className={ `Media-Viewer ${ viewerState.display }` }
onTouchStart={ handleTouchStart }
onTouchMove={ handleTouchMove }
onTouchEnd={ handleTouchEnd }
>
<img className={ `Media-Viewer-Content ${ state.can1.pos }` } alt={ '' }
src={ state.can1.source }
style={{
transform: current === 'can1' ? `translateX(${ touchState.movement }px)` : '',
transitionDuration: `${ canvas.can1.trans ? '' : '0s' }`,
}}
/>
<img className={ `Media-Viewer-Content ${ state.can2.pos }` } alt={ '' }
src={ state.can2.source }
style={{
transform: current === 'can2' ? `translateX(${ touchState.movement }px)` : '',
transitionDuration: `${ canvas.can2.trans ? '' : '0s' }`,
}}
/>
<img className={ `Media-Viewer-Content ${ state.can3.pos }` } alt={ '' }
src={ state.can3.source }
style={{
transform: current === 'can3' ? `translateX(${ touchState.movement }px)` : '',
transitionDuration: `${ canvas.can3.trans ? '' : '0s' }`,
}}
/>
<div className="Modal-User-Interface-Wrapper">
<div className="Information-Box">
<h4>{ `${ state.counter }/${ mediaJSON.length }` }</h4>
</div>
<Anchor
altClass="icon"
fileName="cross.svg"
style={{ position: 'absolute', top: '0', right: '0', opacity: '.5' }}
clicked={ handleClose }
/>
<Anchor
altClass="icon"
fileName="arrow.svg"
style={{ transform: 'rotate(180deg)' }}
clicked={ ()=>content.prev() }
/>
<Anchor
altClass="icon"
fileName="arrow.svg"
clicked={ ()=>content.next() }
/>
</div>
</div>
);
}
export default MediaViewer;
和 CSS
.Media-Viewer {
display: flex;
position: fixed;
justify-content: space-around;
align-items: center;
height: calc(100*var(--vh));
width: 100vw;
height: 100vh;
top: 0; left: 0;
overflow: hidden;
z-index: 10;
transition: opacity .4s ease;
}
.Media-Viewer.hide,
.Media-Viewer.show {
opacity: 0;
}
.Media-Viewer.hidden {
display: none;
}
.Media-Viewer-Content {
display: flex;
position: absolute;
height: 100%;
width: 100%;
object-fit: contain;
top: 0; left: 0;
z-index: 9;
transition: transform .4s ease;
will-change: transform;
}
.Media-Viewer-Content.center {
transform: translate3d(0,0,0);
}
.Media-Viewer-Content.right {
transform: translate3d(100vw,0,0);
}
.Media-Viewer-Content.left {
transform: translate3d(-100vw,0,0);
}
.Modal-User-Interface-Wrapper {
display: flex;
position: relative;
justify-content: space-between;
align-items: center;
width: calc( 100vw - (100vw - 300px) / 10 );
opacity: .6;
height: calc(100*var(--vh));
z-index: 10;
transition: opacity 1.2s ease;
transition-delay: 4s;
}
.Media-Viewer .Anchor {
display: flex;
position: relative;
height: 3.5rem;
width: 3.5rem;
padding: 1rem .7rem 1rem 1rem;
opacity: .1;
margin-bottom: 1.7rem;
border-radius: 4rem;
cursor: pointer;
transition: opacity, background-color .7s ease;
z-index: 10;
transition-duration: .44s;
}
.Media-Viewer .Anchor:hover,
.Media-Viewer:active .Modal-User-Interface-Wrapper,
.Media-Viewer-Content:active .Modal-User-Interface-Wrapper {
opacity: .9;
transition-duration: .1s;
transition-delay: 0s;
}
.Media-Viewer .Anchor:hover {
opacity: .7;
background-color:rgba(0, 0, 0, 0.089);
}
/* overlay */
.Media-Viewer::before {
content: '';
position: absolute;
height: 100vh;
width: 100vw;
top: 0;
z-index: 8;
background-color: rgba(0, 0, 0, 0.932);
backdrop-filter: blur(7px);
-webkit-backdrop-filter: blur(10px);
z-index: -1;
}
.Information-Box {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: .4rem; right: 0; left: 0;
margin: 0 auto;
height: 2.7rem;
width: calc(3rem + 10vw);
background: rgb(220,148,87);
background: linear-gradient(133deg, rgba(32, 28, 24, 0.719) 0%, rgba(43, 32, 26, 0.582) 100%);
border-radius: .6rem;
z-index: 10;
}
.Information-Box h4 {
position: absolute;
color: #fff;
font-size: .85rem;
font-weight: 700;
text-shadow: 1px 2px 5px rgb(1, 4, 5);
}
也许你会在这里找到有用的东西..