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) {}
			},
相关推荐
IT 行者10 小时前
Web逆向工程AI工具:JSHook MCP,80+专业工具让Claude变JS逆向大师
开发语言·javascript·ecmascript·逆向
devlei11 小时前
从源码泄露看AI Agent未来:深度对比Claude Code原生实现与OpenClaw开源方案
android·前端·后端
程序员 沐阳11 小时前
JavaScript 内存与引用:深究深浅拷贝、垃圾回收与 WeakMap/WeakSet
开发语言·javascript·ecmascript
Jagger_12 小时前
周末和AI肝了两天,终于知道:为什么要把AI当做实习生
前端
weixin_4561648312 小时前
vue3 子组件向父组件传参
前端·vue.js
沉鱼.4412 小时前
第十二届题目
java·前端·算法
Setsuna_F_Seiei12 小时前
CocosCreator 游戏开发 - 多维度状态机架构设计与实现
前端·cocos creator·游戏开发
Bigger13 小时前
CodeWalkers:让 AI 助手化身桌面宠物,陪你敲代码的赛博伙伴!
前端·app·ai编程
cyclv14 小时前
无网络地图展示轨迹,地图瓦片下载,绘制管线
前端·javascript
土豆125014 小时前
Tauri 入门与实践:用 Rust 构建你的下一个桌面应用
前端·rust