import React from 'react'
import "./index.css";
import { IConfig, SampleObject, gradientObject } from "../common/types";
interface IProps {
config: IConfig,
left: number,
top: number,
hideContainer: () => void,
cancel?: () => void,
confirm?: (v: gradientObject[]) => void,
colorChange?: (v: gradientObject[]) => void,
}
interface IState {
samples: SampleObject[],
opacity: number | string,
domOne: {
isMoving: boolean
},
domTwo: {
isMoving: boolean
},
domThree: {
isMoving: boolean
},
domSample: {
isMoving: boolean
},
pureValue: string,
type: 'linear-gradient',
angle: number,
//操作的浮标下标,因为要给浮标设置颜色
sampleIndex: number,
stepVal: number | string,
stepScaleVal: number | null | string
}
type rgb = {
r?: number,
g?: number,
b?: number
}
export class ColorContainer extends React.Component<IProps, IState> {
rgba = { r: 0, g: 0, b: 0, a: 1 }
hsb = { h: 0, s: 100, b: 100 }
Initial = null; //默认的初始颜色
setTime = null; //延时器timer
throttle = null; //节流timer
dotOneRef = React.createRef<HTMLDivElement>(); // DotTwo节点
dotTwoRef = React.createRef<HTMLDivElement>(); // DotTwo节点
dotThreeRef = React.createRef<HTMLDivElement>(); // DotThree节点
colorPickerContainerRef = React.createRef<HTMLDivElement>(); // ColorPickerContainer节点
PanelContainerRef = React.createRef<HTMLDivElement>() // PanelContainer节点
sampleRef = React.createRef<HTMLDivElement>() // PanelContainer节点
domTwoDiscolorationRef = React.createRef<HTMLDivElement>()
domThreeDiscolorationRef = React.createRef<HTMLDivElement>()
constructor(props:IProps ) {
super(props)
this.state = {
domOne: {
isMoving: false
},
domTwo: {
isMoving: false,
},
domThree: {
isMoving: false,
},
domSample: {
isMoving: false,
},
opacity: 0, //点击并拖动透明设置条设置
// sample上面的颜色数组,如果不是纯色,最终返回的结果从samples遍历获取
samples:[
{
color: '#667db8ed',
step: 0.2
},{
color: '#6f66b8ed',
step: 0.5
}
],
pureValue: '', //#aaaaaaaa,如果是纯色,最终返回的结果是这个
//类型,线性渐变还是径向渐变
//linear-gradient线性渐变
//radial-gradient径向渐变
type: 'linear-gradient',
//角度
angle: 90,
//操作的浮标下标,因为要给浮标设置颜色
sampleIndex: 0,
stepVal: 0,
stepScaleVal: 0
}
}
componentDidMount() {
// 读取props里面的数据并设置到state中
const { config } = this.props
this.setState({
samples: JSON.parse(JSON.stringify(config.list))
})
// 获取第一个浮标的透明度并设置
const { samples } = this.state
let sampleItemOpacity = + (this.hex2int(samples[0].color.slice(-2)) / 255).toFixed(2)
this.setState({
opacity: sampleItemOpacity,
stepVal: (+samples[0].step).toFixed(3),
stepScaleVal: config.stepScale == null ? null : this.numberMul(samples[0].step, config.stepScale)
})
this.dotThreeRef.current!.style.left = (sampleItemOpacity * 216 - 7.5) + 'px'
}
clickPanel = (evt: any) => {
const { domOne } = this.state
if( domOne.isMoving ) {
return;
}
evt = evt.nativeEvent
let posX = evt.clientX - this.colorPickerContainerRef.current!.offsetLeft - 7.5 - this.props.left
let posY = evt.clientY - this.colorPickerContainerRef.current!.offsetTop - 7.5 - this.props.top
this.dotOneRef.current!.style.top = posY + 'px'
this.dotOneRef.current!.style.left = posX + 'px'
this.panelCalculation(posX, posY)
}
changePickedColor = (evt: any) => {
this.setState({
domOne: {
...this.state.domOne,
isMoving: true
}
})
evt.stopPropagation()
evt = evt.nativeEvent
// let dotOneTop = evt.offsetY > 0 ? evt.offsetY : 0;
// let dotOneLeft = evt.offsetX > 0 ? evt.offsetX : 0;
document.onmousemove = (e) => {
e.preventDefault();
let oneTop = e.clientY - (this.colorPickerContainerRef.current!.offsetTop) - this.props.top - 7.5
oneTop = oneTop <= -7.5 ? -7.5 : oneTop;
oneTop = oneTop >= 116.5 ? 116.5 : oneTop;
let oneLeft = e.clientX - (this.colorPickerContainerRef.current!.offsetLeft) - this.props.left -7.5
oneLeft = oneLeft <= -7.5 ? -7.5 : oneLeft;
oneLeft = oneLeft >= 209.5 ? 209.5 : oneLeft;
this.dotOneRef.current!.style.top = oneTop + "px";
this.dotOneRef.current!.style.left = oneLeft + "px";
// 计算面板中的dot1所在位置的颜色
this.panelCalculation(
(e.clientX - this.colorPickerContainerRef.current!.offsetLeft - 7.5 ),
(e.clientY - this.colorPickerContainerRef.current!.offsetTop -7.5 ),
)
}
document.onmouseup = () => {
document.onmousemove = null;
document.onmouseup = null;
this.setState({
domOne: {
...this.state.domOne,
isMoving: false
}
})
}
}
panelCalculation = (x: number, y: number) => {
let MaxLeft = Math.max(0, Math.min(x, 216));
let MaxTop = Math.max(0, Math.min(y, 124));
this.hsb.s = 100 * MaxLeft / 216;
this.hsb.b = 100 * (124 - MaxTop) / 124;
this.changeColor();
}
// 按下并移动按钮,移动DomTwo位置,并改变panel颜色
changeDomTwoPosition = (event: any) => {
this.setState({
domTwo: {
...this.state.domTwo,
isMoving: true
}
})
event.stopPropagation()
event.nativeEvent.stopImmediatePropagation()
let dotTwoLeft = event.nativeEvent.offsetX > 0 ? event.nativeEvent.offsetX : 0;
let domTowEle = this.dotTwoRef.current!;
let colorPickerContainerDom = this.colorPickerContainerRef.current!;
document.onmousemove = (evt) => {
evt.preventDefault();
evt.stopImmediatePropagation()
let twoLeft = evt.clientX - (colorPickerContainerDom.offsetLeft + dotTwoLeft) - this.props.left
// let twoLeft = evt.offsetX - colorPickerContainerDom.offsetLeft
twoLeft = twoLeft <= -7.5 ? -7.5 : twoLeft
twoLeft = twoLeft >= 209.5 ? 209.5 : twoLeft
// 设置滑轮的位置和panel的背景色
domTowEle.style.left = twoLeft + 'px'
this.changePanelBgColor(evt.clientX - colorPickerContainerDom.offsetLeft - this.props.left)
}
document.onmouseup = () => {
document.onmousemove = null
document.onmouseup = null
this.setState({
domTwo: {
...this.state.domTwo,
isMoving: false
}
})
}
}
clickDiscoloration = (evt: any) => {
if( evt.target !== this.domTwoDiscolorationRef.current ) {
return;
}
const { domTwo } = this.state
if( domTwo.isMoving ) {
return;
}
evt = evt.nativeEvent
this.dotTwoRef.current!.style.left = (evt.offsetX - 7.5) + "px";
this.changePanelBgColor(evt.offsetX);
}
//参数x是domTow移动的位置
changePanelBgColor = (x: number) => {
let Left = Math.max(0, Math.min(x, 216));
this.hsb.h = 360 * Left / 216;
let rgb = this.hsbToRgb({
h: this.hsb.h,
s: 100,
b: 100
});
this.PanelContainerRef.current!.style.backgroundColor = 'rgba(' + rgb.r + ',' + rgb.g + ',' +
rgb.b + ',' + this.rgba.a + ')';
this.changeColor();
}
// 点击透明带设置透明度
setOpacity = ( evt: any ) => {
if( evt.target !== this.domThreeDiscolorationRef.current ) {
return
}
const { domThree } = this.state
if( domThree.isMoving ) {
return;
}
evt.stopPropagation()
evt = evt.nativeEvent
evt.stopImmediatePropagation()
this.dotThreeRef.current!.style.left = (evt.offsetX - 7.5) + "px";
this.setState({
opacity: (evt.offsetX / 216).toFixed(2)
},()=>{
this.changeColor()
})
}
// 点击并拖动透明度设置条
changeDomThreePosition = (event: any) => {
this.setState({
domThree: {
...this.state.domThree,
isMoving: true
}
})
event.stopPropagation()
event.nativeEvent.stopImmediatePropagation()
let dotThreeLeft = event.nativeEvent.offsetX > 0 ? event.nativeEvent.offsetX : 0;
let domThreeEle = this.dotThreeRef.current
let colorPickerContainerDom = this.colorPickerContainerRef.current
document.onmousemove = (evt) => {
evt.preventDefault();
evt.stopImmediatePropagation()
let threeLeft = evt.clientX - (colorPickerContainerDom!.offsetLeft + dotThreeLeft) - this.props.left
// let threeLeft = evt.offsetX - dotThreeLeft
threeLeft = threeLeft <= -7.5 ? -7.5 : threeLeft
threeLeft = threeLeft >= 209.5 ? 209.5 : threeLeft
// 设置滑轮的位置和panel的背景色
domThreeEle!.style.left = threeLeft + 'px'
this.setState({
opacity: ((threeLeft + 7.5) / 216).toFixed(2)
},()=>{
this.changeColor()
})
}
document.onmouseup = () => {
document.onmousemove = null
document.onmouseup = null
this.setState({
domThree: {
...this.state.domThree,
isMoving: false
}
})
}
}
// 点击sample后添加一个sample
addSample = ( evt: any ) => {
if( evt.target !== this.sampleRef.current ) {
return
}
const { domSample } = this.state
if( domSample.isMoving ) {
return;
}
evt = evt.nativeEvent
let posX = evt.offsetX - 5
let samples = JSON.parse(JSON.stringify( this.state.samples ))
samples.push({
color: '#ff0000ff',
step: posX / 216
})
this.setState({
samples
})
}
// 双击sample以删除
removeSample = (idx: number) => {
let samples = JSON.parse(JSON.stringify( this.state.samples ))
const { config } = this.props
if( samples.length < 2 ) {
return console.error('浮标不能少于2个')
}
samples.splice(idx, 1)
this.setState({
samples,
sampleIndex: 0,
stepVal: (+samples[0].step).toFixed(3),
stepScaleVal: config.stepScale == null ? null : this.numberMul(samples[0].step, config.stepScale)
})
}
// 改变旋转角度,参数的意思是要改多少
// changeAngle = (chanedAngle) => {
// const { angle } = this.state
// if( typeof chanedAngle === 'number' ) {
// return this.setState({
// angle: angle + chanedAngle
// })
// }
// // 如果chanedAngle是一个事件
// if( chanedAngle.code !== 'Enter' ) {
// return
// }
// let val = + (chanedAngle.nativeEvent.srcElement.value)
// this.setState({
// angle: angle + val
// })
// }
// 点击并移动sample浮块
// 参数是事件对象和sample数组的下标
moveSample = (event: any, idx: number) => {
// 获取当前sample的透明度并设置
const { samples } = this.state
const { config } = this.props
let sampleItemOpacity = + (this.hex2int(samples[idx].color.slice(-2)) / 255).toFixed(2)
this.setState({
domSample: {
...this.state.domSample,
isMoving: true
},
sampleIndex: idx,
opacity: sampleItemOpacity,
stepVal: samples[idx].step.toFixed(3),
stepScaleVal: config.stepScale == null ? null : this.numberMul(parseFloat(samples[idx].step.toFixed(3)), config.stepScale).toFixed(3)
})
this.dotThreeRef.current!.style.left = (sampleItemOpacity * 216 - 7.5) + 'px'
event.stopPropagation()
event.nativeEvent.stopImmediatePropagation()
let left = event.nativeEvent.offsetX > 0 ? event.nativeEvent.offsetX : 0;
let colorPickerContainerDom = this.colorPickerContainerRef.current
document.onmousemove = (evt) => {
evt.preventDefault();
evt.stopImmediatePropagation()
let sampleLeft = evt.clientX - (colorPickerContainerDom!.offsetLeft + left) - this.props.left
sampleLeft = sampleLeft <= 0 ? 0 : sampleLeft
sampleLeft = sampleLeft >= 216 ? 216 : sampleLeft
let samples = JSON.parse(JSON.stringify( this.state.samples ))
samples[idx].step = sampleLeft / 216
this.setState({
samples,
stepVal: samples[idx].step.toFixed(3),
stepScaleVal: config.stepScale == null ? null : this.numberMul(samples[idx].step, config.stepScale).toFixed(3)
})
}
document.onmouseup = () => {
document.onmousemove = null
document.onmouseup = null
this.setState({
domSample: {
...this.state.domSample,
isMoving: false
},
})
}
}
changeColor = () => {
let rgb = this.hsbToRgb(this.hsb);
this.rgba.r = rgb.r;
this.rgba.g = rgb.g;
this.rgba.b = rgb.b;
const { opacity } = this.state
const { samples , sampleIndex} = this.state
const tempSamples = JSON.parse(JSON.stringify(samples))
tempSamples[sampleIndex].color = this.rgbToHex(rgb, opacity)
this.setState(()=>({
samples: tempSamples
}))
}
// // 点击按钮,改变颜色的类型(径向,渐变,纯色)
// changeType = (type) => {
// this.setState({
// type
// })
// }
//颜色类型转换
hsbToRgb = (hsb: any) => {
var rgb:rgb = {};
var h = hsb.h;
var s = hsb.s * 255 / 100;
var v = hsb.b * 255 / 100;
if (s === 0) {
rgb.r = rgb.g = rgb.b = v;
} else {
var t1 = v;
var t2 = (255 - s) * v / 255;
var t3 = (t1 - t2) * (h % 60) / 60;
if (h === 360) h = 0;
if (h < 60) {
rgb.r = t1;
rgb.b = t2;
rgb.g = t2 + t3
} else if (h < 120) {
rgb.g = t1;
rgb.b = t2;
rgb.r = t1 - t3
} else if (h < 180) {
rgb.g = t1;
rgb.r = t2;
rgb.b = t2 + t3
} else if (h < 240) {
rgb.b = t1;
rgb.r = t2;
rgb.g = t1 - t3
} else if (h < 300) {
rgb.b = t1;
rgb.g = t2;
rgb.r = t2 + t3
} else if (h < 360) {
rgb.r = t1;
rgb.g = t2;
rgb.b = t1 - t3
} else {
rgb.r = 0;
rgb.g = 0;
rgb.b = 0
}
}
return {
r: Math.round(rgb.r),
g: Math.round(rgb.g),
b: Math.round(rgb.b)
};
}
hex2int = (hex: any) => {
var len = hex.length, a = new Array(len), code;
for (var i = 0; i < len; i++) {
code = hex.charCodeAt(i);
if (48<=code && code < 58) {
code -= 48;
} else {
code = (code & 0xdf) - 65 + 10;
}
a[i] = code;
}
return a.reduce(function(acc, c) {
acc = 16 * acc + c;
return acc;
}, 0);
}
rgbToHex = ({ r,g,b }:{r: number, g: number, b: number}, opacity: number | string | undefined = 0) => {
let hex = "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)
let HexOpacity = ((opacity as number) * 255).toString(16).slice(0,2)
if( (+opacity) === 0 ) {
HexOpacity = '00'
}
// 如果最后是 .如a.,b.,要改为a0,b0
if( HexOpacity.indexOf('.') > -1 ) {
HexOpacity = HexOpacity.slice(0,-1) + '0'
}
return hex + HexOpacity
}
// 计算数字相乘
numberMul(arg1: number, arg2: number){
var m=0,s1=arg1.toString(),s2=arg2.toString();
try{
m+=s1.split(".")[1].length;
}catch(e){}
try{
m+=s2.split(".")[1].length;
}catch(e){}
return Number(s1.replace(".",""))*Number(s2.replace(".",""))/Math.pow(10,m);
}
// 计算数字相除
division(arg1: number, arg2: number){
var t1 = 0, t2 = 0, r1, r2;
try {
t1 = (arg1 + "").split(".")[1].length;
} catch (e) { }
try {
t2 = arg2.toString().split(".")[1].length;
} catch (e) { }
r1 = Number((arg1 + "").replace(".", ""));
r2 = Number(arg2.toString().replace(".", ""));
//放大倍数后两个数相除 后,乘以两个小数位数长度相减后的10的次幂
var money = this.numberMul((r1 / r2),Math.pow(10, t2 - t1));
//保留2个小数位数
return money;
}
// 生成最终的颜色样式
generateStyle = () => {
const { type, samples, angle, pureValue } = this.state
if( samples.length === 0 ) {
return 'none'
}
let getStyle = {
'radial-gradient':() => {
let style = `${type}(circle,`
samples.forEach(sample=>{
style += `${sample.color} ${sample.step * 100}%,`
})
style = style.slice(0,-1) + ')'
return style
},
'linear-gradient':() => {
let style = `${type}(${angle}deg,`
samples.forEach(sample=>{
style += `${sample.color} ${sample.step * 100}%,`
})
style = style.slice(0,-1) + ')'
return style
},
'pure':() => {
return pureValue
}
}
return getStyle[type]()
}
changeStep = ( e: any ) => {
let iptVal = e.target.value
const samples = JSON.parse(JSON.stringify(this.state.samples))
const { sampleIndex } = this.state
const { config } = this.props
samples[sampleIndex].step = iptVal
this.setState({
samples,
stepVal: iptVal,
stepScaleVal: config.stepScale == null ? null : this.numberMul(iptVal, config.stepScale)
})
}
changeStepScale = ( e: any ) => {
const { stepScale } = this.props.config
let iptVal = e.target.value || 0
const samples = JSON.parse(JSON.stringify(this.state.samples))
const { sampleIndex } = this.state
samples[sampleIndex].step = this.division(iptVal, stepScale)
this.setState({
samples,
stepVal: this.division(iptVal, stepScale),
stepScaleVal: iptVal
})
}
// 和外面的交互
cancel = () => {
this.props.hideContainer()
this.props.cancel && this.props.cancel()
}
confirm = () => {
this.props.hideContainer()
const { samples } = this.state
let val = samples.map(item=>{
return {
step: +((+item.step).toFixed(3)),
color: item.color
}
})
this.props.confirm && this.props.confirm(val)
}
onColorChange = () => {
const { samples } = this.state
let val = samples.map(item=>{
return {
step: +((+item.step).toFixed(3)),
color: item.color
}
})
this.props.colorChange && this.props.colorChange(val)
}
render() {
// 浮标
const { samples, stepVal, stepScaleVal, sampleIndex: sampleIdx} = this.state
const { colorChange } = this.props
const sampleItems = samples.map((sample,sampleIndex)=>{
let left = (+216 * sample.step - 8).toFixed(3)
let borderBottomColor = 'black'
if( sampleIdx === sampleIndex ) {
borderBottomColor = 'red'
}
return (
<div className="sample-item" key={sampleIndex} style={{ left: left + 'px', borderBottomColor}} onMouseDown={( e )=> this.moveSample(e, sampleIndex) } onDoubleClick={ () => this.removeSample(sampleIndex) }></div>
)
})
// 最终生成的渐变色样式,用于展示
const generatedStyle = this.generateStyle();
colorChange && this.onColorChange()
return (
<div className="ColorPickerBox">
{/* <div className="TypeButtons">
<div className="btn" onClick={()=> this.changeType('linear-gradient') }>线性</div>
<div className="btn" onClick={()=> this.changeType('radial-gradient') }>径向</div>
<div className="btn empty" onClick={()=> this.changeType('pure') }>纯色</div>
</div> */}
<div className="sample" ref={this.sampleRef} onClick={ ( e ) => this.addSample(e) } style={{ background: generatedStyle }}>
{ sampleItems }
</div>
{/* 一些输入项 */}
<div className="InputsInteract">
<div className="InputsItem">
<div className="InputsItemLabel">步长</div>
<input className="InputsItemInput" value={ stepVal } onChange={ this.changeStep }/>
</div>
{
stepScaleVal != null &&
<div className="InputsItem">
<div className="InputsItemLabel">合计</div>
<input className="InputsItemInput" value={ stepScaleVal } onChange={ this.changeStepScale }/>
</div>
}
</div>
<div className="ColorPickerContainer" ref={this.colorPickerContainerRef}>
<div className="PanelContainer" ref={this.PanelContainerRef}>
<div className="panel" onClick={ this.clickPanel }>
<div className="variable">
<div className="DotOne" onMouseDown={this.changePickedColor} ref={this.dotOneRef}></div>
</div>
</div>
</div>
{/* 颜色选择条 */}
<div className="BarContainer">
<div className="Discoloration" onClick={ this.clickDiscoloration } ref={ this.domTwoDiscolorationRef }>
<div className="DotTwo" ref={this.dotTwoRef} onMouseDown={this.changeDomTwoPosition}></div>
</div>
</div>
{/* 透明度选择条 */}
<div className="OpacityBar">
<div className="Discoloration" onClick={ this.setOpacity } ref={ this.domThreeDiscolorationRef }>
<div className="DotThree" ref={ this.dotThreeRef } onMouseDown={this.changeDomThreePosition}></div>
</div>
</div>
{/* 操作按钮,删除或者确定 */}
<div className="Interact">
<div className="btn empty" onClick={ this.cancel }>取消</div>
<div className="btn" onClick={ this.confirm }>确定</div>
</div>
</div>
</div>
)
}
}