本期我们要做的是一个四方位云台操控器(也是遥控器),我们需要方向的 icon(自己找或者自己画~)和 ahooks 的 useInterval(ahooks 真好用啊 )
# 1. 实现目标
- 长按可以设定每 x 秒触发一次接口
- 点击也可以移动
- ... 没了(就是这么简单)
# 2. 实现方法
export const DIRECTION_MAP: any = { | |
top: 1, | |
bottom: 6, | |
left: 4, | |
right: 5, | |
}; | |
function Ptz(props: IPtzProps) { | |
const [interval, setInterval] = useState<any>(null); | |
const directionRef = React.useRef(''); | |
useEffect(() => { | |
enterPTZ(); | |
return () => { | |
exitPTZ(); | |
}; | |
}, []); | |
useInterval( | |
async () => { | |
if (directionRef.current) { | |
await controlPTZ({ | |
direction: DIRECTION_MAP[directionRef.current], | |
}).then(() => { | |
//console.log (`向 ${direction} 移一点`); | |
}); | |
} | |
}, | |
interval, | |
{ immediate: true } | |
); | |
const handlePress = (e: any) => { | |
directionRef.current = e.target.dataset.direction; | |
setInterval(interval + 1000); // 调用频率 | |
}; | |
return ( | |
<div className="control-wrapper"> | |
<div className="control-circle" /> | |
<div className="control-round" /> | |
<div | |
className="control-btn control-top" | |
data-direction="top" | |
onMouseDown={handlePress} | |
onMouseUp={() => { | |
setInterval(null); | |
}} | |
> | |
<Icon className="top-btn" type="arrowsolidlev1up" /> | |
</div> | |
<div | |
className="control-btn control-left" | |
data-direction="left" | |
onMouseDown={handlePress} | |
onMouseUp={() => { | |
setInterval(null); | |
}} | |
> | |
<Icon className="left-btn" type="arrowsolidlev1down" /> | |
</div> | |
<div | |
className="control-btn control-bottom" | |
data-direction="bottom" | |
onMouseDown={handlePress} | |
onMouseUp={() => { | |
setInterval(null); | |
}} | |
> | |
<Icon className="bottom-btn" type="arrowsolidlev1down" /> | |
</div> | |
<div | |
className="control-btn control-right" | |
data-direction="right" | |
onMouseDown={handlePress} | |
onMouseUp={() => { | |
setInterval(null); | |
}} | |
> | |
<Icon className="right-btn" type="arrowsolidlev1up" /> | |
</div> | |
</div> | |
); | |
} | |
export default Ptz; |
然后是最主要的 css
.control-wrapper { | |
position: absolute; | |
bottom: 60px; | |
right: 40px; | |
width: 150px; | |
height: 150px; | |
border-radius: 50%; | |
// background-color: rgba(0, 0, 0, 0.65); | |
.top-btn{ | |
position: absolute; | |
top: 0; | |
left: 50%; | |
transform: translate(-50%, 24px) rotate(45deg); | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
box-sizing: border-box; | |
color: rgba(255,255,255, .75); | |
z-index: 2; | |
pointer-events: none; | |
} | |
.left-btn{ | |
position: absolute; | |
top: 50%; | |
left: 0; | |
transform: translate(24px, -50%) rotate(45deg); | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
color: rgba(255,255,255, .75); | |
z-index: 2; | |
box-sizing: border-box; | |
pointer-events: none; | |
} | |
.bottom-btn { | |
position: absolute; | |
bottom: 0; | |
left: 50%; | |
transform: translate(-50%, -24px) rotate(-45deg); | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
color: rgba(255,255,255, .75); | |
z-index: 2; | |
box-sizing: border-box; | |
pointer-events: none; | |
} | |
.right-btn { | |
position: absolute; | |
top: 50%; | |
right: 0; | |
transform: translate(-24px, -50%) rotate(45deg); | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
color: rgba(255,255,255, .75); | |
z-index: 2; | |
box-sizing: border-box; | |
pointer-events: none; | |
} | |
.control-btn { | |
position: absolute; | |
width: 62px; | |
height: 62px; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
z-index: 1; | |
box-sizing: border-box; | |
// transition: all .3s linear; | |
// background-color: red; | |
background-color: rgba(0, 0, 0, 0.65); | |
&::after{ | |
content: ''; | |
position: absolute; | |
width: 62px; | |
height: 62px; | |
z-index: 1; | |
} | |
} | |
.control-top { | |
top: 0; | |
left: 50%; | |
transform: translateX(-50%) rotate(-45deg); | |
border-radius: 0 100% 0 0; | |
&:hover { | |
.top-btn{ | |
color: rgba(255,255,255, 1); | |
} | |
} | |
&:hover:after{ | |
background: rgba(255, 255, 255, 0.1); | |
} | |
} | |
.control-top:before { | |
transform: translate(30%, -25%); | |
} | |
.control-top:after { | |
left: 0; | |
bottom: 0; | |
border-radius: 0 100% 0 0; | |
} | |
.control-bottom { | |
left: 50%; | |
bottom: 0; | |
transform: translateX(-50%) rotate(45deg); | |
border-radius: 0 0 100% 0; | |
&:hover { | |
.bottom-btn{ | |
color: rgba(255,255,255, 1); | |
} | |
} | |
&:hover::after{ | |
background: rgba(255, 255, 255, 0.1); | |
} | |
} | |
.control-bottom:before { | |
transform: translate(25%, 25%) rotate(90deg); | |
} | |
.control-bottom:after { | |
top: 0; | |
left: 0; | |
border-radius: 0 0 100% 0; | |
} | |
.control-left { | |
top: 50%; | |
left: 0; | |
transform: translateY(-50%) rotate(45deg); | |
border-radius: 0 0 0 100%; | |
&:hover { | |
.left-btn{ | |
color: rgba(255,255,255, 1); | |
} | |
} | |
&:hover::after{ | |
background: rgba(255, 255, 255, 0.1); | |
} | |
} | |
.control-left:before { | |
transform: translate(-25%, 30%) rotate(180deg); | |
} | |
.control-left:after{ | |
right: 0; | |
top: 0; | |
border-radius: 0 0 0 100%; | |
} | |
.control-right { | |
top: 50%; | |
right: 0; | |
transform: translateY(-50%) rotate(45deg); | |
border-radius: 0 100% 0 0; | |
&:hover { | |
.right-btn{ | |
color: rgba(255,255,255, 1); | |
} | |
} | |
&:hover::after{ | |
background: rgba(255, 255, 255, 0.1); | |
} | |
} | |
.control-right:before { | |
transform: translate(30%, -25%); | |
} | |
.control-right:after { | |
left: 0; | |
bottom: 0; | |
border-radius: 0 100% 0 0; | |
} | |
.control-round { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
width: 56.9px; | |
height: 56.9px; | |
background: #7C7C7C; | |
border-radius: 50%; | |
z-index: 3; | |
} | |
} |
着重讲一下 pointer-events 这个属性
需要加上
pointer-events: none
,none
元素永远不会成为鼠标事件的 target (en-US)。但是,当其后代元素的pointer-events
属性指定其他值时,鼠标事件可以指向后代元素,在这种情况下,鼠标事件将在捕获或冒泡阶段触发父元素的事件侦听器。否则点击到 icon 的时候是没有方向的,也就无法调用接口。