如何在Lit+TypeScript中实现长按颜色缩略图显示上下文菜单
使用Lit和TypeScript实现长按颜色缩略图显示上下文菜单
核心思路
- 实现长按检测:通过监听
mousedown/touchstart启动定时器,mouseup/touchend/mouseleave清除定时器,达到设定阈值(比如500ms)时触发菜单显示 - 绑定目标元素:记录当前被长按的颜色缩略图DOM,确保菜单仅对应目标显示
- 状态管理:用Lit响应式属性控制菜单的可见性与关联目标
完整实现代码
1. 颜色缩略图父组件(含长按逻辑)
import { LitElement, html, css, property, state } from 'lit'; import { customElement } from 'lit/decorators.js'; // 假设已有rgbToHex工具函数 function rgbToHex(r: number, g: number, b: number): string { return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; } @customElement('color-grid') export class ColorGrid extends LitElement { static styles = css` .color-container { display: flex; flex-wrap: wrap; gap: 8px; padding: 16px; } .color { width: 60px; height: 60px; border-radius: 4px; cursor: pointer; position: relative; } context-menu { position: absolute; display: none; background: #fff; border: 1px solid #e0e0e0; border-radius: 4px; padding: 8px 0; min-width: 120px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); z-index: 100; } context-menu.show { display: block; } `; // 传入的颜色列表数据 @property({ type: Array }) colors = []; // 响应式状态:记录激活的菜单目标与可见性 @state() private activeMenuTarget: HTMLElement | null = null; @state() private isMenuVisible = false; private longPressTimer: number | null = null; private readonly LONG_PRESS_THRESHOLD = 500; // 长按触发阈值(毫秒) render() { return html` <div class="color-container"> ${this.colors.map((color) => html` <div class="color" style="background:${rgbToHex(color.value.r, color.value.g, color.value.b)}" @mousedown=${(e: MouseEvent) => this.startLongPress(e, color)} @mouseup=${this.cancelLongPress} @mouseleave=${this.cancelLongPress} @touchstart=${(e: TouchEvent) => this.startLongPress(e, color)} @touchend=${this.cancelLongPress} @touchcancel=${this.cancelLongPress} ref=${(el: HTMLElement) => (color._el = el)} > <context-menu class=${this.activeMenuTarget === color._el && this.isMenuVisible ? 'show' : ''} .color=${color} @menu-closed=${this.hideMenu} ></context-menu> </div> `)} </div> `; } private startLongPress(event: MouseEvent | TouchEvent, color: any) { // 阻止浏览器默认右键菜单 event.preventDefault(); // 清除已有定时器,避免重复触发 if (this.longPressTimer) clearTimeout(this.longPressTimer); this.longPressTimer = window.setTimeout(() => { this.activeMenuTarget = color._el; this.isMenuVisible = true; }, this.LONG_PRESS_THRESHOLD); } private cancelLongPress() { if (this.longPressTimer) { clearTimeout(this.longPressTimer); this.longPressTimer = null; } } private hideMenu() { this.isMenuVisible = false; this.activeMenuTarget = null; } connectedCallback() { super.connectedCallback(); // 监听全局点击,点击菜单外区域隐藏菜单 document.addEventListener('click', this.handleGlobalClick); } disconnectedCallback() { super.disconnectedCallback(); document.removeEventListener('click', this.handleGlobalClick); } private handleGlobalClick = (e: MouseEvent) => { const activeMenu = this.shadowRoot?.querySelector('context-menu.show'); if (activeMenu && !activeMenu.contains(e.target as Node)) { this.hideMenu(); } }; }
2. 上下文菜单子组件
import { LitElement, html, css, property } from 'lit'; import { customElement } from 'lit/decorators.js'; @customElement('context-menu') export class ContextMenu extends LitElement { static styles = css` .menu-item { padding: 8px 16px; cursor: pointer; font-size: 14px; } .menu-item:hover { background: #f5f5f5; } `; // 接收父组件传入的当前颜色数据 @property() color: any = null; render() { return html` <div class="menu-item" @click=${this.copyHex}>复制HEX值</div> <div class="menu-item" @click=${this.triggerDelete}>删除颜色</div> `; } private copyHex() { const hex = rgbToHex(this.color.value.r, this.color.value.g, this.color.value.b); navigator.clipboard.writeText(hex); this.closeMenu(); } private triggerDelete() { // 触发自定义事件,通知父组件删除对应颜色 this.dispatchEvent(new CustomEvent('color-delete', { detail: this.color, bubbles: true, composed: true })); this.closeMenu(); } private closeMenu() { this.dispatchEvent(new CustomEvent('menu-closed')); } }
关键细节说明
- 跨设备兼容:同时处理鼠标和触摸事件,适配桌面与移动端
- 精准关联:通过给每个颜色元素绑定引用,确保菜单只在被长按的缩略图上显示
- 交互优化:全局点击隐藏菜单,避免菜单停留在页面上影响操作
- 组件解耦:子菜单通过自定义事件与父组件通信,保持组件独立性
内容的提问来源于stack exchange,提问作者Rue Vitale
相关产品推荐
相关产品推荐

