React Hooks H5移动端实现色盘取色值

用react开发时,项目中有个色盘的需求,花了好长时间调研,现在记录下色盘实现。

色盘的实现主要用到了 colorsys 这个插件 ,用来转换色值;版本  "colorsys": "1.0.17";

手势用到了onTouchStart、onTouchMove、onTouchEnd 这三种触摸方法,话不多说,开始上代码:

 

/* eslint-disable jsx-a11y/alt-text */
import React, { useEffect, useReducer, useRef, useState } from "react"
import { connect } from "react-redux";
import colorsys from "colorsys"; // 颜色转换
import ColorImg from "./colorwheel_image.png"; // 色图图片
import './index.less';



const CuscolorBox = (props: any) => {
    const { dispatchupdataControlTimer, dispatchUiStreams, dispatchToast, uiStreams, deviceState } = props; // 默认导出 不使用可以自行修改
    const thumbSizeW = 40; // 滑点的宽高
    const radius = window.innerWidth / 2 * 0.78; // 半径
    const refRadian = useRef<any>(null);
    const [currentColor, setCurrentColor] = useState<any>('#ffffff'); // 初始颜色默认值
    const [offset, setOffset] = useState<any>({
        x: '',
        y: ''
    }); // 滑点位置

   // 监听色值,根据色值获取当前滑点位置
    useEffect(() => {
        // 根据初始的颜色值回去图标的坐标
        const hsv = colorsys.hex2Hsv(currentColor)
        // console.log('初始值颜色' + [hsv.h, hsv.s, hsv.v])

        //角度
        let angle = 180 - hsv.h
        // 在色盘中的步长
        let length = (radius / 100) * hsv.s
        // 
        let offsett = hypotenuse(length, angle)
        const offx = radius - offsett.x - thumbSizeW / 4
        const offy = radius - offsett.y - thumbSizeW / 4
        setOffset({
            x: offx,
            y: offy,
        })
    }, [currentColor])

    //已知角度和斜边,求直角边
    const hypotenuse = (long: any, angle: any) => {
        //获得弧度
        var radian = 2 * Math.PI / 360 * angle;
        return {
            x: Math.cos(radian) * long,
            y: Math.sin(radian) * long
        };
    }

    // 根据坐标的位置 获取颜色值
    const updateColor = (x: any, y: any, moving: boolean) => {
        const { angle, maxX, maxY, length } = getPointValues(x, y)
        let rads: any = length / radius
        const rad = rads > 1 ? 1 : rads
        const hsv = { h: angle, s: 100 * rad, v: 100 };

        const currentColor = colorsys.hsv2Hex(hsv)
        setCurrentColor(currentColor)
        console.log('获取当前的颜色' + [currentColor, angle])
        //滑动结束 发送当前颜色值
        if (moving === false) {
           console.log(hsv) // touchEnd 之后获取最终的颜色值
        }
    }

    // 获取实际坐标的 角度 坐标值 步长
    const getPointValues = (locationX: any, locationY: any) => {
        // 求斜边的长度
        let offsetX = Math.abs(radius - locationX)
        let offsetY = Math.abs(radius - locationY)
        let length = Math.sqrt(offsetX * offsetX + offsetY * offsetY)// 斜边长度

        //求角度
        // let angle = Math.atan2(offsetY, offsetX) * (180 / Math.PI)
        let angle = getPointAngle(radius - locationX, radius - locationY)


        // console.log('是否出界'+[locationX,locationY,angle])
        // 最终的坐标
        let maxX = locationX - thumbSizeW / 2//this.lastX + ges.dx
        let maxY = locationY - thumbSizeW / 2//this.lastY + ges.dy

        if (length > radius) {
            // 超出边界
            let maxOffsetX = radius * (locationX - radius) / length
            let maxOffsetY = radius * (radius - locationY) / length
            maxX = radius + maxOffsetX - thumbSizeW / 2
            maxY = radius - maxOffsetY - thumbSizeW / 2
            length = radius
        }
        return {
            angle: angle,
            maxX: maxX,
            maxY: maxY,
            length: length,
        }

    }


    const getPointAngle = (x: any, y: any) => {

        // 此时的角度是从左边为 0°开始 顺时针 到右边为 180° 逆时针到最右边为 -180° 
        let angle = Math.atan2(y, x) * (180 / Math.PI)
        // 此转换为左边 0° 顺时针转一圈为 360°
        angle = 180 - angle
        // console.log('获取角度' + angle)
        return angle
    }

    const starthandle = (evt: any) => {
       // 当开始滑动时页面不能跟着滚动 所以需要设置 但是两指同事触摸会导致页面跑到最顶端 所以处理触摸的是几指,只有一指才会触发
        if(evt.touches.length === 1) {
        document.documentElement.style.overflow = 'hidden';
        // scrollTop = document.body.scrollTop || document.documentElement.scrollTop || window.pageYOffset;
        // document.body.style.position = 'fixed'
        // document.body.style.top = `-${scrollTop}px`
        // document.body.style.width = `100%`;
        }

    }

    const handle = (e: any) => {

        const xl = e.changedTouches[0].clientX;
        const yl = e.changedTouches[0].clientY;
        let nx = xl - refRadian.current.getBoundingClientRect().left;
        let ny = yl - refRadian.current.getBoundingClientRect().top;
        updateColor(nx, ny, true)
    }

    const endhandle = (e: any) => {
        // document.body.style.position = 'relative'
        // document.body.style.top = `0px`
        // document.body.style.width = `auto`
        // document.body.scrollTop = document.documentElement.scrollTop = scrollTop
        document.documentElement.style.overflow = ''
        const xl = e.changedTouches[0].clientX;
        const yl = e.changedTouches[0].clientY;
        let nx = xl - refRadian.current.getBoundingClientRect().left;
        let ny = yl - refRadian.current.getBoundingClientRect().top;
        updateColor(nx, ny, false)


    }

    // 控制示例
    return <div
        className="color-wheel-box"
        ref={refRadian}
        onTouchStart={starthandle}
        onTouchMove={handle}
        onTouchEnd={endhandle}
    >
        <img src={ColorImg} style={{ alignItems: 'center', position: 'absolute' }} />
        <div
            className="radian-box"
            style={{
                top: offset.y,
                left: offset.x,
                backgroundColor: currentColor,
            }}>
        </div>
    </div>
}

CSS样式中 touch-action 也是不可少的 上面用js把页面不跟着滚动,只在iOS上可实现,但在安卓不行,所以样式文件加上touch-action后安卓页面

不会滚的

CSS文件:

.color-wheel-box {
    width: 2.94rem;
    height: 2.94rem;
    margin: 0 auto;
    position: relative;
    touch-action: none; 
    img {
        width: 100%;
        height: 100%;
        z-index: 1;
    }

    .radian-box {
        position: absolute;
        z-index: 10;
        // background-color: #fff;
        width: .2rem;
        height: .2rem;
        border-radius: 50%;
        // display: flex;
        // justify-content: center;
        // align-items: center;
        border: .03rem solid #FFFFFF;
        box-shadow: 0 .01rem .02rem 0 rgba(0, 0, 0, 0.21);

        // .point-size {
        //     width: .17rem;
        //     height: .17rem;
        //     // border: .01rem solid #FFFFFF;

        //     border-radius: 50%;
        // }
    }
}

 

 

 上面这个是标准色图 ,以这个为参考;

 

最终形成可滑动、可点击:

 

posted @ 2022-07-07 09:37  微笑时很美n  阅读(537)  评论(0编辑  收藏  举报