预览效果
代码
逻辑部分,命名为panelPPT.tsx
import { Key, memo, useEffect, useRef, useState } from "react"
import sty from './panelPPT.module.scss'
export declare type PPTItemType = {
key: Key
imgsrc: string
title?: string
}
declare interface PanelPPTProps {
items: PPTItemType[]
/**切换间隔,毫秒,默认3000 */
duration?: number
onItemClick?: (key: Key) => void
className?:string
style?: React.CSSProperties
}
/**面板-轮播图 */
const PanelPPT = memo((props: PanelPPTProps) => {
const logic = PanelPPTLogic(props)
return (
<div className={`${sty.root} ${props.className || ''}`}
style={props.style}
ref={logic.setDomContainer}
onMouseOver={logic.stop}
onMouseLeave={logic.start}
>
<div className={sty.scrollbox}>
<ul className={sty['list-bg']} style={{ left: `${logic.listOffsetX}%` }}>
{
props.items.map(d => (
<li className={sty.listitem} key={d.key} style={{ backgroundImage: `url(${d.imgsrc})` }} />
))
}
</ul>
</div>
<ul className={sty['list-small']}>
{
props.items.map((d, i) => (
<li className={`${sty.listitem} ${i === logic.index ? sty.focus : ''}`}
key={d.key}
title={d.title}
onMouseOver={() => logic.handleMouseover(d)}
onMouseLeave={logic.start}
onClick={() => logic.handleItemClick(d)}
style={{ backgroundImage: `url(${d.imgsrc})` }} />
))
}
</ul>
</div>
)
})
export default PanelPPT
const PanelPPTLogic = (props: PanelPPTProps) => {
const ctrlState = useRef({
timer: undefined as undefined | NodeJS.Timer,
index: 0
})
const [domContainer, setDomContainer] = useState<HTMLDivElement | null>()
const [listOffsetX, setListOffsetX] = useState(0)
const [index, setIndex] = useState(0)
const start = () => {
stop()
ctrlState.current.timer = setInterval(() => {
if (ctrlState.current.index < props.items.length - 1) {
ctrlState.current.index++
} else {
ctrlState.current.index = 0
}
setListOffsetX(-ctrlState.current.index * 100)
setIndex(ctrlState.current.index)
}, props.duration || 3000)
}
const stop = () => {
clearInterval(ctrlState.current.timer)
}
const handleMouseover = (item: PPTItemType) => {
stop()
ctrlState.current.index = props.items.indexOf(item)
setListOffsetX(-ctrlState.current.index * 100)
setIndex(ctrlState.current.index)
}
const handleItemClick = (item: PPTItemType) => {
if (props.onItemClick) {
props.onItemClick(item.key)
}
}
useEffect(() => {
start()
return () => {
stop()
}
}, [props.items])
return {
domContainer, setDomContainer,
listOffsetX,
ctrlState,
index,
start,
stop,
handleMouseover,
handleItemClick
}
}
样式部分,命名为panelPPT.module.scss
.root {
height : 400px;
width : 100%;
position: relative;
.scrollbox {
width : 100%;
height : 100%;
overflow: hidden;
position: relative;
.list-bg {
width : 100%;
height : 100%;
white-space: nowrap;
transition : all .5s ease-in-out;
position : absolute;
.listitem {
width : 100%;
height : 100%;
display : inline-block;
background-size: cover;
}
}
}
.list-small {
position : absolute;
bottom : -5px;
display : flex;
justify-content: center;
gap : 1.6rem;
width : 100%;
.listitem {
width : 176px;
height : 99px;
background : rgba(255, 255, 255, 0.3);
border : 1px solid rgba(255, 255, 255, 0.45);
backdrop-filter: blur(16px);
border-radius : 6px;
flex-shrink : 0;
overflow : hidden;
transition : all .3s ease-in-out;
cursor : pointer;
background-size: cover;
filter : drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.08));
&:hover,
&.focus {
transform : scale(1.1);
border : 2px solid #fff;
box-shadow: 0px 6px 30px 5px rgba(0, 0, 0, 0.05), 0px 16px 24px 2px rgba(0, 0, 0, 0.04), 0px 8px 10px -5px rgba(0, 0, 0, 0.08);
}
}
}
}
@media only screen and (min-width: 1440px) {
.root {
height: 50rem;
}
}
@media only screen and (max-width: 1440px) {
.root {
height: 40rem;
}
}