uniapp 颜色卡条拖动

1:创建组件HueColorBar.vue

javascript 复制代码
<template>
	<view class="hueBarWrap">
		<view
			:id="barId"
			class="hueBar"
			@touchstart.stop.prevent="onTouch"
			@touchmove.stop.prevent="onTouch"
			@tap="onTap"
			@mousedown.stop.prevent="onMouseDown"
		>
			<view class="thumb" :style="{ left: thumbLeftPx + 'px', backgroundColor: thumbColor }"></view>
		</view>
	</view>
</template>

<script>
	export default {
		name: 'HueColorBar',
		props: {
			// Vue2 v-model: value + input
			value: {
				type: String,
				default: '#FF00FF'
			},
			// 圆点大小(px)
			thumbSize: {
				type: Number,
				default: 14
			}
		},
		data() {
			return {
				barId: `hue_bar_${Math.random().toString(16).slice(2)}`,
				barLeft: 0,
				barWidth: 0,
				hue: 0,
				thumbLeftPx: 0,
				mouseDragging: false
			}
		},
		computed: {
			thumbColor() {
				// 圆点保持白色描边更贴近截图,这里用纯白
				return '#FFFFFF'
			}
		},
		watch: {
			value: {
				immediate: true,
				handler(v) {
					const hue = this.hexToHue(v)
					if (Number.isFinite(hue)) {
						this.hue = hue
						this.syncThumb()
					}
				}
			}
		},
		mounted() {
			this.$nextTick(() => {
				this.measure()
			})
			// H5 下支持鼠标拖拽
			if (typeof window !== 'undefined') {
				window.addEventListener('mousemove', this.onMouseMove, { passive: false })
				window.addEventListener('mouseup', this.onMouseUp, { passive: true })
				window.addEventListener('mouseleave', this.onMouseUp, { passive: true })
			}
		},
		beforeDestroy() {
			if (typeof window !== 'undefined') {
				window.removeEventListener('mousemove', this.onMouseMove)
				window.removeEventListener('mouseup', this.onMouseUp)
				window.removeEventListener('mouseleave', this.onMouseUp)
			}
		},
		methods: {
			measure() {
				const query = uni.createSelectorQuery().in(this)
				query.select(`#${this.barId}`).boundingClientRect((rect) => {
					if (!rect) return
					this.barLeft = rect.left || 0
					this.barWidth = rect.width || 0
					this.syncThumb()
				}).exec()
			},
			onTap(e) {
				// 小程序/H5 点击都走这里
				this.updateFromEvent(e)
			},
			onMouseDown(e) {
				this.mouseDragging = true
				this.updateFromEvent(e)
			},
			onMouseMove(e) {
				if (!this.mouseDragging) return
				if (e && e.preventDefault) e.preventDefault()
				this.updateFromEvent(e)
			},
			onMouseUp() {
				this.mouseDragging = false
			},
			onTouch(e) {
				this.updateFromEvent(e)
			},
			updateFromEvent(e) {
				if (!this.barWidth) this.measure()
				let x = null
				const t = (e && e.touches && e.touches[0]) || (e && e.changedTouches && e.changedTouches[0])
				if (t && typeof t.clientX === 'number') x = t.clientX
				else if (e && typeof e.clientX === 'number') x = e.clientX
				// 某些端 tap 事件没有 clientX,退化为不处理
				if (x === null) return

				const half = this.thumbSize / 2
				const raw = x - this.barLeft
				const clamped = Math.max(0, Math.min(this.barWidth, raw))
				const ratio = this.barWidth ? clamped / this.barWidth : 0
				this.hue = Math.round(ratio * 360)
				this.thumbLeftPx = Math.max(-half, Math.min(this.barWidth - half, clamped - half))

				const hex = this.hsvToHex(this.hue, 100, 100)
				this.$emit('input', hex)
				this.$emit('change', { hex, hue: this.hue })
			},
			syncThumb() {
				if (!this.barWidth) return
				const half = this.thumbSize / 2
				const ratio = Math.max(0, Math.min(1, this.hue / 360))
				const x = ratio * this.barWidth
				this.thumbLeftPx = Math.max(-half, Math.min(this.barWidth - half, x - half))
			},
			hsvToHex(h, s, v) {
				const { r, g, b } = this.hsvToRgb(h, s, v)
				return this.rgbToHex(r, g, b)
			},
			hsvToRgb(h, s, v) {
				const _s = Math.max(0, Math.min(100, s)) / 100
				const _v = Math.max(0, Math.min(100, v)) / 100
				let _h = ((h % 360) + 360) % 360
				const c = _v * _s
				const x = c * (1 - Math.abs(((_h / 60) % 2) - 1))
				const m = _v - c
				let rp = 0, gp = 0, bp = 0
				if (_h < 60) { rp = c; gp = x; bp = 0 }
				else if (_h < 120) { rp = x; gp = c; bp = 0 }
				else if (_h < 180) { rp = 0; gp = c; bp = x }
				else if (_h < 240) { rp = 0; gp = x; bp = c }
				else if (_h < 300) { rp = x; gp = 0; bp = c }
				else { rp = c; gp = 0; bp = x }
				return {
					r: Math.round((rp + m) * 255),
					g: Math.round((gp + m) * 255),
					b: Math.round((bp + m) * 255)
				}
			},
			rgbToHex(r, g, b) {
				const toHex = (n) => {
					const s = Math.max(0, Math.min(255, n)).toString(16).toUpperCase()
					return s.length === 1 ? '0' + s : s
				}
				return `#${toHex(r)}${toHex(g)}${toHex(b)}`
			},
			hexToHue(hex) {
				if (!hex || typeof hex !== 'string') return 0
				const m = hex.trim().match(/^#?([0-9a-fA-F]{6})$/)
				if (!m) return 0
				const v = m[1]
				const r = parseInt(v.slice(0, 2), 16)
				const g = parseInt(v.slice(2, 4), 16)
				const b = parseInt(v.slice(4, 6), 16)
				return this.rgbToHue(r, g, b)
			},
			rgbToHue(r, g, b) {
				const rp = r / 255, gp = g / 255, bp = b / 255
				const max = Math.max(rp, gp, bp)
				const min = Math.min(rp, gp, bp)
				const d = max - min
				if (d === 0) return 0
				let h = 0
				if (max === rp) h = ((gp - bp) / d) % 6
				else if (max === gp) h = (bp - rp) / d + 2
				else h = (rp - gp) / d + 4
				h = Math.round(h * 60)
				if (h < 0) h += 360
				return h
			}
		}
	}
</script>

<style lang="scss" scoped>
	.hueBarWrap {
		width: 100%;
		display: flex;
		justify-content: flex-end;
	}

	.hueBar {
		position: relative;
		width: 100%;
		height: 10rpx;
		border-radius: 999rpx;
		background: linear-gradient(90deg,
				#FF3B30 0%,
				#FF9500 16%,
				#FFCC00 33%,
				#34C759 50%,
				#5AC8FA 66%,
				#007AFF 83%,
				#AF52DE 100%);
	}

	.thumb {
		position: absolute;
		top: 50%;
		transform: translateY(-50%);
		width: 28rpx;
		height: 28rpx;
		border-radius: 50%;
		border: 2rpx solid rgba(255, 255, 255, 0.9);
		box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.35);
	}
</style>

2:在需要的页面引用组件HueColorBar.vue

javascript 复制代码
<HueColorBar v-model="ambientColor" @change="onAmbientColorChange" />


import HueColorBar from '@/components/HueColorBar.vue'

components: {
			HueColorBar
		},

ambientColor: '#AF52DE'

	onAmbientColorChange({ hex }) {
				console.log(hex,'hex');
				this.ambientColor = hex
				try {
					// uni.setStorageSync('ambientLightColor', hex)
				} catch (e) {}
			},
相关推荐
MichaelJohn2 小时前
qiankun 微前端实战(二):主应用搭建 — 安装、注册与全局状态
前端
Muchen灬2 小时前
【uniapp】(2) uni-ui组件引入
uni-app
ruanCat2 小时前
simple-git-hooks 踩坑实录:钩子装对了却从没触发过,原来是 .git 目录捣的鬼
前端·git·代码规范
兆子龙2 小时前
React Fiber 架构与 Vue 响应式原理深度对比
前端·javascript
用户363858544182 小时前
Query 和 jQuery UI
前端
labixiong2 小时前
React Fiber 架构全景解析(一)
前端·react.js
bluceli2 小时前
JavaScript事件循环深度解析:从宏任务到微任务的执行机制
前端·javascript
Ticnix2 小时前
Vue3 页面切换 / 刷新后恢复滚动位置的实现方案(超精简)
前端·javascript
NPCZ2 小时前
uniapp更新到最新版5.03后报错的解决方案
uni-app