优化进度条组件;重构滑块组件
parent
0cde6f7c02
commit
8461d75a69
|
@ -185,11 +185,12 @@ class Progress extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
#drawLine(v) {
|
#drawLine(v) {
|
||||||
|
let l = v < 2 ? 2 : v > 98 ? 98 : v
|
||||||
return html`
|
return html`
|
||||||
<div class="progress-bar">
|
<div class="progress-bar">
|
||||||
<mark class="thumb" style=${styleMap({ width: v + '%' })}></mark>
|
<mark class="thumb" style=${styleMap({ width: v + '%' })}></mark>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress-value" style=${styleMap({ left: v + '%' })}>
|
<div class="progress-value" style=${styleMap({ left: l + '%' })}>
|
||||||
${v}%
|
${v}%
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
/**
|
/**
|
||||||
* {}
|
* {进度条}
|
||||||
* @author yutent<yutent.io@gmail.com>
|
* @author chensbox<chensbox@foxmail.com>
|
||||||
* @date 2023/03/21 16:14:10
|
* @date 2023/04/28 16:14:10
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { css, bind, unbind, html, Component } from 'wkit'
|
import { css, html, Component, range } from 'wkit'
|
||||||
|
|
||||||
class Slider extends Component {
|
class Slider extends Component {
|
||||||
static props = {
|
static props = {
|
||||||
|
@ -12,325 +12,267 @@ class Slider extends Component {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
observer(val) {
|
observer(val) {
|
||||||
this.$bar && this.initValue(val)
|
this.value = this.#fix(val)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
type: 'info',
|
step: {
|
||||||
max: 100,
|
type: Number,
|
||||||
min: 0,
|
default: 1,
|
||||||
step: 1,
|
observer(v) {
|
||||||
disabled: false,
|
v += ''
|
||||||
readonly: false,
|
this.#decimal = v.split('.').pop().length
|
||||||
vertical: false
|
}
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
observer(v) {
|
||||||
|
this.#updateRange()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
default: 100,
|
||||||
|
observer(v) {
|
||||||
|
this.#updateRange()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
vertical: false,
|
||||||
|
disabled: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#len = 100
|
||||||
|
#stops = 10 // 断点数, 最大只显示10个断点
|
||||||
|
#point = 10 // 每个断点的百分比
|
||||||
|
#value = 0 // 内部的值, 固定使用 0-100范围的值
|
||||||
|
#decimal = 0 //小数位
|
||||||
|
|
||||||
static styles = [
|
static styles = [
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
display: inline-block;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 38px;
|
min-height: 32px;
|
||||||
}
|
}
|
||||||
.slider {
|
|
||||||
|
.container {
|
||||||
|
--line-width: var(--wc-slider-line-width, 6px);
|
||||||
|
--base-color: var(--wc-slider-inactive-color, var(--color-plain-2));
|
||||||
|
--active-color: var(--wc-slider-active-color, var(--color-teal-1));
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
width: 100%;
|
||||||
align-items: center;
|
height: var(--line-width);
|
||||||
height: 100%;
|
-webkit-user-select: none;
|
||||||
}
|
|
||||||
.runway {
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
height: 6px;
|
|
||||||
// width: 100%;
|
|
||||||
background: #e4e7ed;
|
|
||||||
border-radius: 99rem;
|
|
||||||
cursor: pointer;
|
|
||||||
.bar {
|
|
||||||
flex: 1;
|
|
||||||
pointer-events: none;
|
|
||||||
position: absolute;
|
|
||||||
height: 100%;
|
|
||||||
width: 0%;
|
|
||||||
border-radius: 99rem;
|
|
||||||
background: var(--color-blue-2, #409eff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.dot-wrapper {
|
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 36px;
|
|
||||||
width: 36px;
|
|
||||||
top: 50%;
|
|
||||||
left: 0%;
|
|
||||||
cursor: grab;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
.dot {
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2px solid var(--color-blue-2, #409eff);
|
|
||||||
background: #fff;
|
|
||||||
transition: transform 0.2s ease-in-out;
|
|
||||||
&:hover {
|
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tips {
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-bar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -100%;
|
left: 0;
|
||||||
border-radius: 4px;
|
top: 0;
|
||||||
padding: 10px;
|
width: 100%;
|
||||||
z-index: 2000;
|
height: var(--line-width);
|
||||||
font-size: 12px;
|
border-radius: 32px;
|
||||||
line-height: 1.2;
|
background: var(--base-color);
|
||||||
min-width: 10px;
|
i {
|
||||||
white-space: nowrap;
|
display: none;
|
||||||
color: #fff;
|
|
||||||
background: #303133;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
transition: opacity 0.2s ease-in-out;
|
|
||||||
&.show {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
&:after {
|
|
||||||
position: absolute;
|
|
||||||
border: 6px solid transparent;
|
|
||||||
bottom: -10px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
content: '';
|
|
||||||
border-top-color: #303133;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
//状态
|
// vertical
|
||||||
css`
|
css`
|
||||||
:host([vertical]) {
|
:host([vertical]) {
|
||||||
display: inline-block;
|
width: auto;
|
||||||
width: 38px;
|
height: 100%;
|
||||||
height: 250px;
|
align-items: flex-end;
|
||||||
.slider {
|
|
||||||
justify-content: center;
|
|
||||||
.runway {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column-reverse;
|
|
||||||
width: 6px;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
cursor: pointer;
|
.container {
|
||||||
.bar {
|
width: var(--line-width);
|
||||||
height: 0%;
|
height: 100%;
|
||||||
width: 100%;
|
min-height: 32px;
|
||||||
}
|
|
||||||
.dot-wrapper {
|
|
||||||
left: 50%;
|
|
||||||
top: 100%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.tips {
|
|
||||||
top: 100%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -180%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:host([hide-tooltip]) .tips {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([loading]),
|
.slider-bar {
|
||||||
:host([disabled]) {
|
width: var(--line-width);
|
||||||
pointer-events: none;
|
height: 100%;
|
||||||
cursor: not-allowed;
|
}
|
||||||
opacity: 0.6;
|
|
||||||
|
.thumb {
|
||||||
|
top: auto;
|
||||||
|
transform: translate(calc(var(--line-width) / 2 - 16px), 16px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
//配色
|
|
||||||
css`
|
|
||||||
$colors: (
|
|
||||||
primary: 'teal',
|
|
||||||
info: 'blue',
|
|
||||||
success: 'green',
|
|
||||||
warning: 'orange',
|
|
||||||
danger: 'red',
|
|
||||||
secondary: 'dark',
|
|
||||||
help: 'grey'
|
|
||||||
);
|
|
||||||
|
|
||||||
@loop $t, $c in $colors {
|
// stops
|
||||||
:host([type='#{$t}']) {
|
css`
|
||||||
.bar {
|
:host([show-stops]) {
|
||||||
background-color: var(--color-#{$c}-2);
|
.slider-bar {
|
||||||
|
i {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
width: var(--line-width);
|
||||||
|
height: var(--line-width);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
transform: translateX(-50%);
|
||||||
}
|
}
|
||||||
.dot {
|
}
|
||||||
border-color: var(--color-#{$c}-2);
|
}
|
||||||
|
`,
|
||||||
|
// thumb
|
||||||
|
css`
|
||||||
|
.thumb {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: calc((32px - var(--line-width)) / -2);
|
||||||
|
z-index: 2;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 7px;
|
||||||
|
transform: translateX(-16px);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
display: block;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border: 2px solid var(--active-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
content: '';
|
||||||
|
background: #fff;
|
||||||
|
cursor: grab;
|
||||||
|
transition: transform 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 32px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
background: var(--color-dark-2);
|
||||||
|
color: #fff;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -3px;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background: var(--color-dark-2);
|
||||||
|
content: '';
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&::before {
|
||||||
|
transform: scale(1.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
&::before {
|
||||||
|
cursor: grabbing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
]
|
]
|
||||||
progress = 0
|
|
||||||
mounted() {
|
#updateRange() {
|
||||||
this.$bar = this.$refs.bar
|
let { min, max, step } = this
|
||||||
this.$dotWrap = this.$refs.dotWrap
|
if (min >= max) {
|
||||||
this.$runway = this.$refs.runway
|
throw new Error('Slider props "min >= max"')
|
||||||
this.$tips = this.$refs.tips
|
}
|
||||||
this.accuracy = this.step >= 1 ? 0 : String(this.step).split('.')[1].length
|
let len = max - min
|
||||||
this.initValue(this.value)
|
this.#stops = ~~(len / step)
|
||||||
|
if (this.#stops > 10) {
|
||||||
|
this.#stops = 10
|
||||||
|
}
|
||||||
|
this.#len = len
|
||||||
|
this.#point = +(100 / this.#stops).toFixed(2)
|
||||||
}
|
}
|
||||||
onMousedown(e) {
|
|
||||||
let {
|
|
||||||
value: preValue,
|
|
||||||
step,
|
|
||||||
max,
|
|
||||||
min,
|
|
||||||
progress,
|
|
||||||
vertical,
|
|
||||||
disabled,
|
|
||||||
readOnly,
|
|
||||||
accuracy
|
|
||||||
} = this
|
|
||||||
if (disabled || readOnly) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.$tips.classList.toggle('show')
|
|
||||||
preValue = +preValue || min
|
|
||||||
|
|
||||||
let start = vertical ? e.clientY : e.clientX
|
#fix(val) {
|
||||||
let onMousemove = bind(document, 'mousemove', e => {
|
let { min, max, step } = this
|
||||||
e.preventDefault()
|
let fix
|
||||||
let distance = (vertical ? e.clientY : e.clientX) - start
|
val = Math.max(min, Math.min(val, max))
|
||||||
let scale =
|
val = +val.toFixed(this.#decimal)
|
||||||
(distance /
|
|
||||||
(vertical ? this.$runway.clientHeight : this.$runway.clientWidth)) *
|
|
||||||
(vertical ? -1 : 1)
|
|
||||||
|
|
||||||
let diff =
|
fix = +(val % step).toFixed(this.#decimal)
|
||||||
accuracy === 0 ? Math.round(scale * (max - min)) : scale * (max - min)
|
if (fix !== step) {
|
||||||
let newProgress = progress + Math.floor(scale * 100)
|
if (fix < step / 2) {
|
||||||
let newValue =
|
val -= fix
|
||||||
accuracy === 0 ? Math.floor(preValue + diff) : preValue + diff
|
|
||||||
newValue = Math.max(newValue, min)
|
|
||||||
newValue = Math.min(newValue, max)
|
|
||||||
|
|
||||||
if (accuracy === 0) {
|
|
||||||
if (newValue % step) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let _newValue = Math.round(newValue * Math.pow(10, accuracy))
|
val = val - fix + step
|
||||||
let _step = step * Math.pow(10, accuracy)
|
|
||||||
if (_newValue % _step) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.value = newValue.toFixed(accuracy)
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
this.setProgress(vertical ? 100 - newProgress : newProgress)
|
|
||||||
})
|
|
||||||
this.$emit('input')
|
|
||||||
})
|
|
||||||
|
|
||||||
let onMouseup = bind(document, 'mouseup', () => {
|
|
||||||
unbind(document, 'mousemove', onMousemove)
|
|
||||||
unbind(document, 'mouseup', onMouseup)
|
|
||||||
this.$tips.classList.toggle('show')
|
|
||||||
this.$emit('change')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onClick(e) {
|
|
||||||
if (e.target !== this.$refs.runway) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let { max, min, step, vertical, disabled, readOnly, accuracy } = this
|
|
||||||
if (disabled || readOnly) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let { clientWidth, clientHeight } = e.target
|
|
||||||
let { offsetX, offsetY } = e
|
|
||||||
let range = max - min
|
|
||||||
let scale =
|
|
||||||
(vertical ? offsetY : offsetX) / (vertical ? clientHeight : clientWidth)
|
|
||||||
|
|
||||||
step *= Math.pow(10, accuracy)
|
|
||||||
|
|
||||||
let newValue =
|
|
||||||
+(scale * range + min).toFixed(accuracy) * Math.pow(10, accuracy)
|
|
||||||
let mod = +(newValue % step).toFixed(accuracy)
|
|
||||||
if (mod) {
|
|
||||||
let half = step / 2
|
|
||||||
if (mod > half) {
|
|
||||||
newValue += step - mod
|
|
||||||
} else {
|
|
||||||
newValue -= mod
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newValue *= Math.pow(10, -accuracy)
|
this.#value = Math.round(((val - min) * 100) / (max - min))
|
||||||
this.value = (vertical ? range - newValue : newValue).toFixed(accuracy)
|
return val
|
||||||
let progress = Math.floor(((newValue - min) / range) * 100)
|
}
|
||||||
this.setProgress(progress)
|
|
||||||
this.$emit('change')
|
#seek(ev) {
|
||||||
this.$emit('input')
|
let { clientWidth, clientHeight } = ev.target
|
||||||
if (!this.timeout) {
|
let { min, max, step } = this
|
||||||
this.$tips.classList.toggle('show')
|
let val = 0
|
||||||
|
if (this.vertical) {
|
||||||
|
val = (clientHeight - ev.offsetY) / clientHeight
|
||||||
} else {
|
} else {
|
||||||
clearTimeout(this.timeout)
|
val = ev.offsetX / clientWidth
|
||||||
this.timeout = null
|
|
||||||
}
|
}
|
||||||
this.timeout = setTimeout(() => {
|
val *= this.#len
|
||||||
this.$tips.classList.toggle('show')
|
val += min
|
||||||
this.timeout = null
|
|
||||||
}, 1000)
|
this.value = this.#fix(val)
|
||||||
}
|
}
|
||||||
initValue(val) {
|
|
||||||
let { max, min, vertical, disabled, readOnly } = this
|
get #activeStyle() {
|
||||||
if (disabled || readOnly) {
|
let v = this.#value
|
||||||
return
|
let deg = this.vertical ? 0 : '90deg'
|
||||||
}
|
return `background:linear-gradient(${deg}, var(--active-color) ${v}%, transparent ${v}%)`
|
||||||
let range = max - min
|
}
|
||||||
val = Math.max(val, min)
|
|
||||||
val = Math.min(val, max)
|
get #thumbStyle() {
|
||||||
this.value = val
|
let v = this.#value
|
||||||
let progress = Math.floor(((val - min) / range) * 100)
|
if (this.vertical) {
|
||||||
progress = vertical ? 100 - progress : progress
|
return `bottom:${v}%`
|
||||||
this.setProgress(progress)
|
|
||||||
}
|
|
||||||
setProgress(val) {
|
|
||||||
let { vertical } = this
|
|
||||||
val = Math.floor(val)
|
|
||||||
val = Math.min(val, 100)
|
|
||||||
val = Math.max(val, 0)
|
|
||||||
if (vertical) {
|
|
||||||
this.$bar.style.height = `${100 - val}%`
|
|
||||||
this.$dotWrap.style.top = `${val}%`
|
|
||||||
this.$tips.style.top = `${val}%`
|
|
||||||
this.progress = 100 - val
|
|
||||||
} else {
|
|
||||||
this.$bar.style.width = `${val}%`
|
|
||||||
this.$dotWrap.style.left = `${val}%`
|
|
||||||
this.$tips.style.left = `${val}%`
|
|
||||||
this.progress = val
|
|
||||||
}
|
}
|
||||||
|
return `left:${v}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`<div class="slider" ref="slider">
|
let n = this.hasAttribute('show-stops') ? this.#stops : 1
|
||||||
<div ref="runway" class="runway" @click=${this.onClick}>
|
|
||||||
<div class="bar" ref="bar"></div>
|
return html`
|
||||||
<div class="dot-wrapper" ref="dotWrap" @mousedown=${this.onMousedown}>
|
<main class="container">
|
||||||
<div class="dot"></div>
|
<div class="slider-bar">
|
||||||
|
${range(1, n).map(i => {
|
||||||
|
return html`<i style="left:${i * this.#point}%"></i>`
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div
|
||||||
<div class="tips" ref="tips">${this.value}</div>
|
class="slider-bar"
|
||||||
</div>`
|
style=${this.#activeStyle}
|
||||||
|
@click=${this.#seek}
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div class="thumb" style=${this.#thumbStyle}>
|
||||||
|
<mark class="value">${this.value}</mark>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue