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%; // } } }
上面这个是标准色图 ,以这个为参考;
最终形成可滑动、可点击: