优化color组件

master
yutent 2023-11-24 18:38:06 +08:00
parent 0e66261757
commit f119d77c4f
2 changed files with 179 additions and 180 deletions

View File

@ -13,9 +13,14 @@ import {
outsideClick, outsideClick,
clearOutsideClick, clearOutsideClick,
nextTick, nextTick,
offset offset,
styleMap
} from 'wkit' } from 'wkit'
const EV_OPTION = { once: true }
const DOC = document
const ROOT = document.documentElement
// H: 色相, S: 饱和度, B/V: 亮度 // H: 色相, S: 饱和度, B/V: 亮度
export function hsb2rgb(hsb) { export function hsb2rgb(hsb) {
var h = hsb.h var h = hsb.h
@ -133,29 +138,13 @@ class Color extends Component {
value: { value: {
type: String, type: String,
default: '', default: '',
attribute: false,
observer(val) { observer(val) {
if (!this.panelShow) { if (this.#show) {
this.#calc(val) this.#calc(val)
} }
} }
}, },
disabled: false, disabled: false
readonly: false
}
// 临时的value, 组件内的操作, 修改的是这个值, 避免直接修改value触发太多的计算
#value = ''
#x = 0 // 场景触点的X坐标
#y = 0 // 场景触点的Y坐标
#ht = 0 // 颜色池的触点坐标
#at = 100 // 透明度条的触点坐标
#sceneBg = '#ff0000'
#alphaBg = '#ff0000'
cache = {
hsb: { h: 0, s: 100, b: 100 },
rgba: { r: 255, g: 0, b: 0, a: 100 }
} }
static styles = [ static styles = [
@ -163,10 +152,10 @@ class Color extends Component {
:host { :host {
display: inline-flex; display: inline-flex;
} }
.color-picker { .container {
position: relative; position: relative;
width: 36px; width: 32px;
height: 36px; height: 32px;
} }
.alpha-bg { .alpha-bg {
background: linear-gradient( background: linear-gradient(
@ -195,8 +184,8 @@ class Color extends Component {
display: flex; display: flex;
width: 100%; width: 100%;
height: 100%; height: 100%;
border: 2px solid var(--color-plain-2); border: 1px solid var(--color-grey-2);
border-radius: 6px; border-radius: 3px;
cursor: pointer; cursor: pointer;
transition: box-shadow 0.15s linear; transition: box-shadow 0.15s linear;
@ -204,7 +193,8 @@ class Color extends Component {
width: 100%; width: 100%;
height: 100%; height: 100%;
border: 3px solid #fff; border: 3px solid #fff;
border-radius: 6px; border-radius: 3px;
background: var(--value);
outline: none; outline: none;
} }
@ -216,8 +206,9 @@ class Color extends Component {
// .color-panel // .color-panel
css` css`
.color-panel { .color-panel {
display: none; display: var(--show, none);
position: absolute; position: absolute;
z-index: 1;
left: 0; left: 0;
top: 38px; top: 38px;
width: 310px; width: 310px;
@ -234,27 +225,14 @@ class Color extends Component {
position: relative; position: relative;
width: 280px; width: 280px;
height: 180px; height: 180px;
background: #f00; background: linear-gradient(180deg, transparent, #000),
linear-gradient(90deg, #fff, transparent), var(--scene);
&::before,
&::after {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
content: '';
}
&::before {
background: linear-gradient(to right, #fff, transparent);
}
&::after {
background: linear-gradient(to bottom, transparent, #000);
}
.thumb { .thumb {
position: absolute; position: absolute;
z-index: 1;
left: var(--x);
top: var(--y);
width: 0; width: 0;
height: 0; height: 0;
@ -262,9 +240,9 @@ class Color extends Component {
display: block; display: block;
width: 10px; width: 10px;
height: 10px; height: 10px;
border: 1px solid #fff;
border-radius: 50%; border-radius: 50%;
background: rgba(32, 32, 32, 0.3); background: rgba(32, 32, 32, 0.3);
box-shadow: 0 0 0 1px #fff;
transform: translate(-5px, -5px); transform: translate(-5px, -5px);
content: ''; content: '';
} }
@ -290,7 +268,7 @@ class Color extends Component {
.thumb { .thumb {
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: var(--ht);
width: 12px; width: 12px;
height: 0; height: 0;
@ -305,14 +283,6 @@ class Color extends Component {
content: ''; content: '';
} }
} }
.hue {
position: relative;
display: block;
width: 12px;
height: 180px;
appearance: slider-vertical;
opacity: 0;
}
} }
} }
.alpha-box { .alpha-box {
@ -328,12 +298,12 @@ class Color extends Component {
top: 0; top: 0;
width: 100%; width: 100%;
height: 12px; height: 12px;
background: linear-gradient(to right, transparent, #f00); background: linear-gradient(90deg, transparent, var(--alpha));
} }
.thumb { .thumb {
position: absolute; position: absolute;
left: 100%; left: var(--at);
top: 0; top: 0;
width: 0; width: 0;
height: 12px; height: 12px;
@ -415,9 +385,39 @@ class Color extends Component {
} }
} }
} }
`,
css`
:host([disabled]) {
opacity: 0.6;
.preview {
cursor: not-allowed;
&:focus-within {
box-shadow: unset;
}
}
}
` `
] ]
#show = false
// 临时的value, 组件内的操作, 修改的是这个值, 避免直接修改value触发太多的计算
#value = ''
#x = 0 // 场景触点的X坐标
#y = 0 // 场景触点的Y坐标
#ht = 0 // 颜色池的触点坐标
#at = 100 // 透明度条的触点坐标
#sceneBg = '#ff0000'
#alphaBg = '#ff0000'
#cache = {
hsb: { h: 0, s: 100, b: 100 },
rgba: { r: 255, g: 0, b: 0, a: 100 }
}
#calc(val) { #calc(val) {
var isHex var isHex
var rgb var rgb
@ -431,53 +431,119 @@ class Color extends Component {
isHex = /^#[0-9a-f]{3,6}$/.test(val) isHex = /^#[0-9a-f]{3,6}$/.test(val)
if (isHex) { if (isHex) {
Object.assign(this.cache.rgba, hex2rgb(val), { a: 100 }) Object.assign(this.#cache.rgba, hex2rgb(val), { a: 100 })
} else { } else {
var res = val.match(/rgba?\((\d+),\s*?(\d+),\s*?(\d+)[,\s]*?([\d\.]+)?\)/) var res = val.match(/rgba?\((\d+),\s*?(\d+),\s*?(\d+)[,\s]*?([\d\.]+)?\)/)
if (res) { if (res) {
this.cache.rgba = { r: +res[1], g: +res[2], b: +res[3], a: 100 } this.#cache.rgba = { r: +res[1], g: +res[2], b: +res[3], a: 100 }
if (res[4] !== undefined) { if (res[4] !== undefined) {
this.cache.rgba.a = ~~(res[4] * 100) this.#cache.rgba.a = ~~(res[4] * 100)
} }
} else { } else {
return return
} }
} }
this.cache.hsb = rgb2hsb(this.cache.rgba) this.#cache.hsb = rgb2hsb(this.#cache.rgba)
} }
toggleColorPanel() { toggleColorPanel() {
this.$refs.panel.style.display = 'block' if (this.disabled) {
this.panelShow = true return
}
this.#show = true
this.#updateView() this.#updateView()
} }
// 透明度变化 // 透明度变化
alphaChange(ev) { #changeAlpha(ev) {
var a = ev.target.value let a = +ev.target.value
var { r, g, b } = this.cache.rgba let { r, g, b } = this.#cache.rgba
this.cache.rgba.a = a this.#cache.rgba.a = a
this.#updateView() this.#updateView()
} }
// 色彩池变化 // 色彩池变化
hueChange(ev) { #changeHue(h) {
var h = 360 - ev.target.value h = h < 0 ? 0 : h > 360 ? 360 : h
var { s, b } = this.cache.hsb let { s, b } = this.#cache.hsb
var rgba = this.cache.rgba let rgba = this.#cache.rgba
var hsb = { h, s, b } let hsb = { h, s, b }
Object.assign(rgba, hsb2rgb(hsb)) Object.assign(rgba, hsb2rgb(hsb))
this.cache.hsb = hsb this.#cache.hsb = hsb
this.cache.rgba = rgba this.#cache.rgba = rgba
this.#updateView() this.#updateView()
} }
//
#changeColor(x, y) {
let { hsb, rgba } = this.#cache
hsb.s = ~~((100 * x) / 280)
hsb.b = ~~((100 * (180 - y)) / 180)
Object.assign(rgba, hsb2rgb(hsb))
this.#cache.hsb = hsb
this.#cache.rgba = rgba
this.#updateView()
}
#sceneMousedown(ev) {
let { x, y } = ev
let { left, top } = offset(ev.currentTarget)
let { scrollLeft, scrollTop } = ROOT
let _x = left - scrollLeft
let _y = top - scrollTop
this.#changeColor(x - _x, y - _y)
let callback = bind(DOC, 'mousemove', ({ x, y }) => {
x -= _x
y -= _y
x = x < 0 ? 0 : x > 280 ? 280 : x
y = y < 0 ? 0 : y > 180 ? 180 : y
this.#changeColor(x, y)
})
bind(DOC, 'mouseup', _ => unbind(DOC, 'mousemove', callback), EV_OPTION)
}
#poolMousedown(ev) {
let { y } = ev
let { top } = offset(ev.currentTarget)
let { scrollTop } = ROOT
let { clientHeight: h } = ev.currentTarget
let _y = top - scrollTop
y -= _y
this.#changeHue(~~((y / h) * 360))
let callback = bind(DOC, 'mousemove', ({ y }) => {
y -= _y
this.#changeHue(~~((y / h) * 360))
})
bind(DOC, 'mouseup', _ => unbind(DOC, 'mousemove', callback), EV_OPTION)
}
#close(clear) {
if (this.#show) {
this.#show = false
this.#calc(this.value)
this.#updateView()
}
}
#submit() {
this.value = this.#value
this.#close()
this.$emit('change', { data: this.#value })
}
#updateView() { #updateView() {
var { hsb, rgba } = this.cache var { hsb, rgba } = this.#cache
var sceneBg, color, alphaBg var sceneBg, color, alphaBg
var x, y var x, y
@ -503,135 +569,68 @@ class Color extends Component {
this.$requestUpdate() this.$requestUpdate()
} }
#changeColor(x, y) {
let { hsb, rgba } = this.cache
hsb.s = ~~((100 * x) / 280)
hsb.b = ~~((100 * (180 - y)) / 180)
Object.assign(rgba, hsb2rgb(hsb))
this.cache.hsb = hsb
this.cache.rgba = rgba
this.#updateView()
}
closePanel() {
if (this.hasOwnProperty('panelShow')) {
this.$refs.panel.style.display = ''
this.#calc(this.value)
this.#updateView()
delete this.panelShow
}
}
submit() {
this.$refs.panel.style.display = ''
this.value = this.#value
this.$emit('change', { data: this.#value })
delete this.panelShow
}
mounted() { mounted() {
var handleMove
// 更新一次视图 // 更新一次视图
nextTick(_ => this.#updateView()) this.#updateView()
bind(this.$refs.scene, 'mousedown', ev => {
ev.preventDefault()
var { pageX, pageY } = ev
var { left, top } = offset(this.$refs.scene)
var x = pageX - left
var y = pageY - top
this.#changeColor(x, y)
handleMove = bind(document, 'mousemove', ev => {
ev.preventDefault()
var { pageX, pageY } = ev
var x = pageX - left
var y = pageY - top
var rgb
x = x < 0 ? 0 : x > 280 ? 280 : x
y = y < 0 ? 0 : y > 180 ? 180 : y
this.#changeColor(x, y)
})
})
this.handleUp = bind(document, 'mouseup', ev => {
ev.preventDefault()
unbind(document, 'mousemove', handleMove)
})
// 点击外部区别时,还原之前的颜色值 // 点击外部区别时,还原之前的颜色值
this._outsideFn = outsideClick(this.$refs.panel, ev => { this._outsideFn = outsideClick(this, ev => {
this.closePanel() this.#close()
}) })
} }
unmounted() { unmounted() {
clearOutsideClick(this._outsideFn) clearOutsideClick(this._outsideFn)
unbind(document, 'mouseup', this.handleUp)
} }
render() { render() {
let styles = styleMap({
'--show': this.#show ? 'block' : 'none',
'--value': this.#value,
'--scene': this.#sceneBg,
'--x': this.#x + 'px',
'--y': this.#y + 'px',
'--ht': this.#ht + 'px',
'--at': this.#at + '%',
'--alpha': this.#alphaBg
})
return html` return html`
<div class="color-picker"> <main class="container" style=${styles}>
<section class="preview alpha-bg" @click=${this.toggleColorPanel}> <section class="preview alpha-bg" @click=${this.toggleColorPanel}>
<span style="background-color:${this.#value}" tabindex="0"></span> <span tabindex=${this.disabled ? -1 : 0}></span>
</section> </section>
<div class="color-panel" ref="panel">
<div class="color-panel">
<section class="dashboard"> <section class="dashboard">
<div <div class="scene" @mousedown.prevent=${this.#sceneMousedown}>
class="scene" <i class="thumb"></i>
style="background-color:${this.#sceneBg}"
ref="scene"
>
<span
class="thumb"
style="left: ${this.#x}px; top:${this.#y}px"
></span>
</div> </div>
<div class="pool"> <div class="pool" @mousedown.prevent=${this.#poolMousedown}>
<span class="thumb hue-thumb" style="top:${this.#ht}px"></span> <i class="thumb"></i>
<input
class="hue"
max="360"
min="0"
step="1"
type="range"
@input=${this.hueChange}
/>
</div> </div>
</section> </section>
<section class="alpha-box alpha-bg"> <section class="alpha-box alpha-bg">
<div <div class="bar"></div>
class="bar" <i class="thumb"></i>
style="background:linear-gradient(to right, transparent, ${this
.#alphaBg})"
></div>
<span class="thumb alpha-thumb" style="left:${this.#at}%"></span>
<input <input
class="alpha" class="alpha"
max="100" max="100"
min="0" min="0"
step="1" step="1"
type="range" type="range"
@input=${this.alphaChange} @input=${this.#changeAlpha}
/> />
</section> </section>
<section class="input-box"> <section class="input-box">
<input class="input" :value=${this.#value} maxlength="28" /> <input class="input" :value=${this.#value} maxlength="25" />
<a class="clear" @click=${this.closePanel}>清除</a> <a class="clear" @click=${_ => this.#close(true)}>清除</a>
<a tabindex="0" class="submit" @click=${this.submit}>确定</a> <a tabindex="0" class="submit" @click=${this.#submit}>确定</a>
</section> </section>
</div> </div>
</div> </main>
` `
} }
} }

View File

@ -42,14 +42,6 @@ class Slider extends Component {
lazy: 'num!0' lazy: 'num!0'
} }
#len = 100
#stops = 10 // 断点数, 最大只显示10个断点
#point = 10 // 每个断点的百分比
#value = 0 // 内部的值, 固定使用 0-100范围的值
#decimal = 0 //小数位
#stamp = 0
static styles = [ static styles = [
css` css`
:host { :host {
@ -208,6 +200,14 @@ class Slider extends Component {
` `
] ]
#len = 100
#stops = 10 // 断点数, 最大只显示10个断点
#point = 10 // 每个断点的百分比
#value = 0 // 内部的值, 固定使用 0-100范围的值
#decimal = 0 //小数位
#stamp = 0
#updateRange() { #updateRange() {
let { min, max, step } = this let { min, max, step } = this
if (min >= max) { if (min >= max) {