|
|
@@ -0,0 +1,207 @@
|
|
|
+import React, { useCallback, useEffect, useState, useRef } from 'react'
|
|
|
+import styles from './index.module.scss'
|
|
|
+import { callIframeFu } from '@/utils/history'
|
|
|
+import { isPc } from '@/utils/http'
|
|
|
+
|
|
|
+// 圆弧参数
|
|
|
+const arcRadius = 80 // 圆弧半径
|
|
|
+const arcPercentage = 0.263 // 26.3% 完整圆
|
|
|
+const totalArcAngle = arcPercentage * 2 * Math.PI // 1.653 弧度
|
|
|
+const arcStartAngle = -Math.PI / 2 - totalArcAngle / 2 // 起始角度(从左侧水平开始)
|
|
|
+
|
|
|
+function Zlight() {
|
|
|
+ const [flag, setFlag] = useState(false)
|
|
|
+ const [num, setNum] = useState(30)
|
|
|
+ const [numPage, setNumPage] = useState(0)
|
|
|
+ const [baiHeight, setBaiHeight] = useState(0)
|
|
|
+ const startXRef = useRef(0) // 记录拖动起始位置
|
|
|
+ const startNumRef = useRef(30) // 记录拖动起始数值
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ // 将num的范围[-60,60]映射到[0,100]
|
|
|
+ const mappedValue = (num + 60) * (100 / 120)
|
|
|
+ setNumPage(mappedValue)
|
|
|
+ callIframeFu('setLightRotationY', num)
|
|
|
+ }, [num])
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ // 将numPage映射到圆弧角度范围
|
|
|
+ const currentAngle = arcStartAngle + (numPage / 100) * totalArcAngle
|
|
|
+
|
|
|
+ // 计算滑块的垂直位置(相对于圆心)
|
|
|
+ const yOffset = arcRadius * Math.sin(currentAngle)
|
|
|
+
|
|
|
+ // 由于已知numPage=0时top=-3,numPage=50时top=10
|
|
|
+ // 我们需要将yOffset映射到正确的范围
|
|
|
+ const minYOffset = arcRadius * Math.sin(arcStartAngle)
|
|
|
+ const maxYOffset = arcRadius * Math.sin(arcStartAngle + totalArcAngle / 2)
|
|
|
+
|
|
|
+ // 线性映射到目标范围
|
|
|
+ const topValue = -3 + ((yOffset - minYOffset) * (10 - -3)) / (maxYOffset - minYOffset)
|
|
|
+
|
|
|
+ setBaiHeight(topValue)
|
|
|
+ }, [numPage])
|
|
|
+
|
|
|
+ // --------------------电脑端拖动
|
|
|
+ const onMouseDown = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
|
|
+ setFlag(true)
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ const onMouseMove = useCallback(
|
|
|
+ (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
|
|
+ if (flag) {
|
|
|
+ const numTemp = e.movementX / 4
|
|
|
+ let numRes = num + numTemp
|
|
|
+ if (numTemp < 0 && numRes <= -60) numRes = -60
|
|
|
+ if (numTemp > 0 && numRes >= 60) numRes = 60
|
|
|
+ setNum(numRes)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ [flag, num]
|
|
|
+ )
|
|
|
+
|
|
|
+ const onMouseUp = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
|
|
+ setFlag(false)
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ // ---------------------手机端拖动--------------------------
|
|
|
+ const handleDragStart = useCallback(
|
|
|
+ (clientY: number) => {
|
|
|
+ setFlag(true)
|
|
|
+ startXRef.current = clientY
|
|
|
+ startNumRef.current = num
|
|
|
+ },
|
|
|
+ [num]
|
|
|
+ )
|
|
|
+
|
|
|
+ const handleDragMove = useCallback(
|
|
|
+ (clientY: number) => {
|
|
|
+ if (!flag) return
|
|
|
+
|
|
|
+ // 计算移动距离(转换为与movementX类似的效果)
|
|
|
+ const movementX = clientY - startXRef.current
|
|
|
+ const numTemp = movementX / 2 // 保持与原有逻辑一致的灵敏度
|
|
|
+
|
|
|
+ let numRes = startNumRef.current + numTemp
|
|
|
+ if (numTemp < 0 && numRes <= -60) numRes = -60
|
|
|
+ if (numTemp > 0 && numRes >= 60) numRes = 60
|
|
|
+
|
|
|
+ setNum(numRes)
|
|
|
+ },
|
|
|
+ [flag]
|
|
|
+ )
|
|
|
+
|
|
|
+ // 触摸事件处理[1,3](@ref)
|
|
|
+ const onTouchStart = useCallback(
|
|
|
+ (e: React.TouchEvent<HTMLDivElement>) => {
|
|
|
+ if (e.touches.length > 1) return // 避免多点触控干扰[4](@ref)
|
|
|
+ const touch = e.touches[0]
|
|
|
+ handleDragStart(touch.clientY)
|
|
|
+ // e.preventDefault() // 防止页面滚动
|
|
|
+ },
|
|
|
+ [handleDragStart]
|
|
|
+ )
|
|
|
+
|
|
|
+ const onTouchMove = useCallback(
|
|
|
+ (e: React.TouchEvent<HTMLDivElement>) => {
|
|
|
+ if (e.touches.length > 1) return
|
|
|
+ const touch = e.touches[0]
|
|
|
+ handleDragMove(touch.clientY)
|
|
|
+ // e.preventDefault() // 防止页面滚动
|
|
|
+ },
|
|
|
+ [handleDragMove]
|
|
|
+ )
|
|
|
+
|
|
|
+ const onTouchEnd = useCallback((e: React.TouchEvent<HTMLDivElement>) => {
|
|
|
+ setFlag(false)
|
|
|
+ // e.preventDefault()
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ // 添加全局事件处理以确保拖动流畅性[3](@ref)
|
|
|
+ useEffect(() => {
|
|
|
+ if (!isPc) {
|
|
|
+ const handleGlobalMouseMove = (e: MouseEvent) => {
|
|
|
+ if (flag) {
|
|
|
+ handleDragMove(e.clientY)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleGlobalMouseUp = () => {
|
|
|
+ if (flag) {
|
|
|
+ setFlag(false)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleGlobalTouchMove = (e: TouchEvent) => {
|
|
|
+ if (flag && e.touches.length === 1) {
|
|
|
+ handleDragMove(e.touches[0].clientY)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleGlobalTouchEnd = () => {
|
|
|
+ if (flag) {
|
|
|
+ setFlag(false)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 注册全局事件[3](@ref)
|
|
|
+ document.addEventListener('mousemove', handleGlobalMouseMove)
|
|
|
+ document.addEventListener('mouseup', handleGlobalMouseUp)
|
|
|
+ document.addEventListener('touchmove', handleGlobalTouchMove, { passive: false })
|
|
|
+ document.addEventListener('touchend', handleGlobalTouchEnd)
|
|
|
+
|
|
|
+ return () => {
|
|
|
+ // 清理事件监听[3](@ref)
|
|
|
+ document.removeEventListener('mousemove', handleGlobalMouseMove)
|
|
|
+ document.removeEventListener('mouseup', handleGlobalMouseUp)
|
|
|
+ document.removeEventListener('touchmove', handleGlobalTouchMove)
|
|
|
+ document.removeEventListener('touchend', handleGlobalTouchEnd)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, [flag, handleDragMove])
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div
|
|
|
+ id='Zlight'
|
|
|
+ className={styles.Zlight}
|
|
|
+ // 鼠标事件
|
|
|
+ onMouseDown={onMouseDown}
|
|
|
+ onMouseMove={onMouseMove}
|
|
|
+ onMouseUp={onMouseUp}
|
|
|
+ onMouseLeave={() => setFlag(false)}
|
|
|
+ // 触摸事件[1,4](@ref)
|
|
|
+ onTouchStart={onTouchStart}
|
|
|
+ onTouchMove={onTouchMove}
|
|
|
+ onTouchEnd={onTouchEnd}
|
|
|
+ style={{
|
|
|
+ cursor: flag ? 'move' : 'pointer',
|
|
|
+ // 改善移动端触摸体验
|
|
|
+ touchAction: 'none', // 阻止浏览器默认的触摸行为(如滚动)
|
|
|
+ WebkitUserSelect: 'none', // 防止文本选择
|
|
|
+ userSelect: 'none'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div className='Zlbox'>
|
|
|
+ <img className='Zlimg1' src={require('@/assets/sgImg/lightBase.png')} alt='' />
|
|
|
+ <div className='Zlimg2'>
|
|
|
+ <img src={require('@/assets/sgImg/lightAc.png')} alt='' />
|
|
|
+ <div
|
|
|
+ className='Zlkuai'
|
|
|
+ style={{
|
|
|
+ left: numPage + '%',
|
|
|
+ top: baiHeight + 'px',
|
|
|
+ // 添加平滑过渡效果
|
|
|
+ transition: flag ? 'none' : 'all 0.1s ease'
|
|
|
+ }}
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+ <div className='Zlimg3 songFont'>
|
|
|
+ <img id='opacityChange' src={require('@/assets/sgImg/lightBs.png')} alt='' />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+const MemoZlight = React.memo(Zlight)
|
|
|
+export default MemoZlight
|