前两天做页面交互时,突然有个想法:“我想加一个点击时有水波纹的按钮,就像 Android 上那种 Material Ripple 效果,既优雅又带感。”
以前搞这种动画,光是 CSS 和 JS 的交互处理就能折腾一阵子:点击位置的计算、波纹的生成、动画时间、淡出曲线、性能优化……一大堆细节。
但现在,我只在 通义灵码 里轻描淡写地说了一句:
“生成一个按钮,点击时有水波纹动画,模仿 Material Design Ripple 效果。”
没想到 通义灵码 直接给我整了个现成的,自带动画、自适应点击位置、可复用样式的按钮组件,效果干净、流畅,完全不输手写版本。
我想要的水波纹效果是什么样?
我说的“水波纹”并不是那种浮夸的点击特效,而是 Material Design 里的那种:
- 鼠标点击按钮时,以点击点为中心出现一个淡淡的圆形扩散波纹;
- 波纹会逐渐扩散变大,然后渐隐消失;
- 在不同按钮尺寸下,动画自然适配,不突兀;
- 背景色不被覆盖,波纹只是视觉反馈;
- 不阻塞其他交互,响应足够快。
说白了,就是一种“细腻但克制”的美感。
通义灵码 如何“自动生成”这个效果?
通义灵码 的能力在于,它不只是理解按钮该长什么样,它还能理解“水波纹”这三个字背后的交互意图。
我只输入一句自然语言指令:
“生成一个按钮,点击时显示水波纹动画,模拟 Material Ripple 效果。”
通义灵码 给我的组件非常完整:
- 按钮结构:简洁干净,支持文本、图标或任意子内容;
- CSS 动画:用
@keyframes精细模拟波纹扩散和透明度衰减; - 事件逻辑:自动绑定点击事件,根据鼠标点击位置动态生成波纹;
- 性能考虑:波纹元素会在动画结束后自动移除,内存友好;
- 可复用性:生成的是组件化写法,可以多处引用、不重复代码;
- 样式隔离:波纹层与按钮内容分离,保证文字/icon 不被覆盖。
而我,不用动手写一行样式。
点击反馈体验:超出预期的细腻
我原本以为 通义灵码 只是给一个基础版,结果效果出奇地丝滑。点击按钮的那一刻,波纹从鼠标点中心扩散开,透明度控制得刚刚好,没有明显的卡顿或突兀。
更惊喜的是——
- 多次快速点击:波纹能正确叠加,不互相遮盖;
- 适配深色背景:波纹颜色会自动调整为浅色调;
- 移动端长按:也能触发波纹,反馈一致;
- hover 状态:还贴心加了阴影过渡,动静结合。
可以说,这就是标准 UI 体验里“锦上添花”的交互小亮点。
想拓展?通义灵码 还能继续自动加料
我试着继续和 通义灵码 说:
“加一个变体,支持长按触发水波纹,并显示文字提示。”
通义灵码 就自动生成了一个新版本,按钮长按超过 500ms 不但有水波纹,还会在上方弹出一句提示信息,比如:“正在执行,请稍候……”
我又说了一句:
“按钮加图标,放在左边,点击水波纹不遮住图标。”
通义灵码 就更新了按钮结构,图标和文字左右排布,并自动让水波纹绕开图标中心,重点落在点击位置。真的是非常智能的补全能力。
通义灵码 帮我省下的不只是时间
以前,我总觉得一些交互效果就算好看,可能不值得花一两个小时来写。但现在有了 通义灵码 ,我只需要几秒钟就能把想法变成现实,还能自动生成质量不错的代码。
从“我想要一个水波纹按钮”,到“这个按钮已经可以上线用了”,只隔着一行自然语言。
这个过程,不仅提升了效率,更释放了我的创造力。因为我再也不会因为“实现起来太麻烦”而放弃一个好点子了。
想试试的朋友可以这样做:
打开 通义灵码 ,输入这句:
“生成一个按钮,点击时带水波纹动画,模拟 Material Ripple 效果。”
然后看看 通义灵码 给你的惊喜。如果你有更复杂的交互想法,也尽管说出来,它都能帮你“对号入座”。未来的前端开发,也许真的不再是写组件,而是说组件。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Material 水波纹动画效果</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Arial', 'Microsoft YaHei', sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 20px; color: #333; } .header { text-align: center; margin-bottom: 50px; color: white; } .header h1 { font-size: 2.5em; margin-bottom: 10px; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); } .header p { font-size: 1.1em; opacity: 0.9; } .demo-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 30px; max-width: 1200px; width: 100%; } .demo-section { background: rgba(255, 255, 255, 0.95); border-radius: 20px; padding: 30px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); backdrop-filter: blur(10px); } .section-title { font-size: 1.3em; font-weight: bold; margin-bottom: 20px; color: #333; text-align: center; } /* 基础水波纹容器 */ .ripple-container { position: relative; overflow: hidden; cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent; } /* 水波纹动画 */ .ripple { position: absolute; border-radius: 50%; background: rgba(255, 255, 255, 0.6); transform: scale(0); animation: ripple-animation 0.6s linear forwards; pointer-events: none; z-index: 1; } @keyframes ripple-animation { 0% { transform: scale(0); opacity: 1; } 100% { transform: scale(4); opacity: 0; } } /* 长按水波纹动画 */ .ripple-long-press { position: absolute; border-radius: 50%; background: rgba(255, 255, 255, 0.3); transform: scale(0); animation: ripple-long-press-animation 2s ease-out forwards; pointer-events: none; z-index: 1; } @keyframes ripple-long-press-animation { 0% { transform: scale(0); opacity: 0.8; } 50% { transform: scale(2); opacity: 0.6; } 100% { transform: scale(4); opacity: 0; } } /* 基础按钮样式 */ .btn { display: inline-flex; align-items: center; justify-content: center; padding: 12px 24px; margin: 10px; border: none; border-radius: 8px; background: #2196f3; color: white; font-size: 16px; font-weight: 500; transition: all 0.3s ease; position: relative; min-width: 120px; min-height: 48px; } .btn:hover { background: #1976d2; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(33, 150, 243, 0.4); } .btn:active { transform: translateY(0); } /* 图标按钮样式 */ .btn-with-icon { padding-left: 16px; padding-right: 24px; justify-content: flex-start; gap: 12px; } .btn-icon { font-size: 18px; z-index: 2; position: relative; flex-shrink: 0; width: 20px; text-align: center; } .btn-text { z-index: 2; position: relative; } /* 不同颜色的按钮 */ .btn-success { background: #4caf50; } .btn-success:hover { background: #388e3c; box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4); } .btn-warning { background: #ff9800; } .btn-warning:hover { background: #f57c00; box-shadow: 0 4px 12px rgba(255, 152, 0, 0.4); } .btn-danger { background: #f44336; } .btn-danger:hover { background: #d32f2f; box-shadow: 0 4px 12px rgba(244, 67, 54, 0.4); } .btn-secondary { background: #6c757d; } .btn-secondary:hover { background: #545b62; box-shadow: 0 4px 12px rgba(108, 117, 125, 0.4); } /* 圆形按钮 */ .btn-circle { width: 56px; height: 56px; border-radius: 50%; padding: 0; min-width: auto; font-size: 24px; } /* 卡片样式 */ .card { background: #fff; border-radius: 12px; padding: 20px; margin: 10px 0; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; } .card:hover { transform: translateY(-4px); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); } /* 长按提示 */ .long-press-tooltip { position: absolute; top: -40px; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.8); color: white; padding: 8px 12px; border-radius: 4px; font-size: 12px; white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 0.3s ease; z-index: 10; } .long-press-tooltip::after { content: ''; position: absolute; top: 100%; left: 50%; transform: translateX(-50%); border: 4px solid transparent; border-top-color: rgba(0, 0, 0, 0.8); } .long-press-tooltip.show { opacity: 1; } /* 进度指示器 */ .progress-indicator { position: absolute; top: 0; left: 0; width: 100%; height: 4px; background: rgba(255, 255, 255, 0.3); border-radius: 2px; overflow: hidden; opacity: 0; transition: opacity 0.3s ease; } .progress-bar { height: 100%; background: rgba(255, 255, 255, 0.8); width: 0%; transition: width linear; border-radius: 2px; } .progress-indicator.show { opacity: 1; } /* 列表项样式 */ .list-item { display: flex; align-items: center; padding: 16px 20px; margin: 2px 0; background: #fff; border-radius: 8px; cursor: pointer; transition: background-color 0.3s ease; } .list-item:hover { background: #f5f5f5; } .list-icon { margin-right: 16px; font-size: 20px; width: 24px; text-align: center; color: #666; z-index: 2; position: relative; } .list-content { flex: 1; z-index: 2; position: relative; } .list-title { font-weight: 500; margin-bottom: 4px; } .list-subtitle { font-size: 14px; color: #666; } /* 输入框水波纹 */ .input-container { position: relative; margin: 15px 0; } .input-field { width: 100%; padding: 16px; border: 2px solid #ddd; border-radius: 8px; font-size: 16px; background: #fff; transition: border-color 0.3s ease; outline: none; } .input-field:focus { border-color: #2196f3; } /* 开关样式 */ .switch-container { display: flex; align-items: center; justify-content: space-between; padding: 16px 0; margin: 10px 0; } .switch { position: relative; width: 52px; height: 32px; background: #ccc; border-radius: 16px; cursor: pointer; transition: background-color 0.3s ease; } .switch.active { background: #2196f3; } .switch-thumb { position: absolute; top: 2px; left: 2px; width: 28px; height: 28px; background: white; border-radius: 50%; transition: transform 0.3s ease; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); } .switch.active .switch-thumb { transform: translateX(20px); } /* 演示说明 */ .demo-info { background: #e3f2fd; border-left: 4px solid #2196f3; padding: 15px; margin: 20px 0; border-radius: 4px; font-size: 14px; color: #1565c0; } /* 响应式设计 */ @media (max-width: 768px) { .demo-container { grid-template-columns: 1fr; gap: 20px; } .header h1 { font-size: 2em; } .demo-section { padding: 20px; } } /* 自定义水波纹颜色 */ .ripple-blue { background: rgba(33, 150, 243, 0.6); } .ripple-green { background: rgba(76, 175, 80, 0.6); } .ripple-orange { background: rgba(255, 152, 0, 0.6); } .ripple-red { background: rgba(244, 67, 54, 0.6); } .ripple-dark { background: rgba(0, 0, 0, 0.2); } </style> </head> <body> <div class="header"> <h1>🌊 Material 水波纹动画效果</h1> <p>模拟 Material Design Ripple 效果,支持点击、长按和图标按钮</p> </div> <div class="demo-container"> <!-- 基础按钮演示 --> <div class="demo-section"> <div class="section-title">🎯 基础水波纹按钮</div> <div class="demo-info"> 点击按钮查看水波纹效果,支持多种颜色主题 </div> <button class="btn ripple-container" data-ripple-color="white"> 默认按钮 </button> <button class="btn btn-success ripple-container" data-ripple-color="white"> 成功按钮 </button> <button class="btn btn-warning ripple-container" data-ripple-color="white"> 警告按钮 </button> <button class="btn btn-danger ripple-container" data-ripple-color="white"> 危险按钮 </button> </div> <!-- 图标按钮演示 --> <div class="demo-section"> <div class="section-title">🔘 图标按钮水波纹</div> <div class="demo-info"> 水波纹不会遮挡左侧图标,保持图标清晰可见 </div> <button class="btn btn-with-icon ripple-container" data-ripple-color="white"> <span class="btn-icon">📁</span> <span class="btn-text">打开文件</span> </button> <button class="btn btn-success btn-with-icon ripple-container" data-ripple-color="white"> <span class="btn-icon">💾</span> <span class="btn-text">保存文档</span> </button> <button class="btn btn-warning btn-with-icon ripple-container" data-ripple-color="white"> <span class="btn-icon">⚙️</span> <span class="btn-text">设置选项</span> </button> <button class="btn btn-danger btn-with-icon ripple-container" data-ripple-color="white"> <span class="btn-icon">🗑️</span> <span class="btn-text">删除文件</span> </button> </div> <!-- 长按按钮演示 --> <div class="demo-section"> <div class="section-title">👆 长按水波纹效果</div> <div class="demo-info"> 长按按钮1秒以上触发特殊水波纹效果和提示文字 </div> <button class="btn ripple-container long-press-btn" data-ripple-color="white" data-long-press-text="长按已激活!"> <span class="progress-indicator"> <div class="progress-bar"></div> </span> <span class="long-press-tooltip">继续长按激活</span> 长按我试试 </button> <button class="btn btn-success btn-with-icon ripple-container long-press-btn" data-ripple-color="white" data-long-press-text="下载已开始!"> <span class="progress-indicator"> <div class="progress-bar"></div> </span> <span class="long-press-tooltip">长按开始下载</span> <span class="btn-icon">⬇️</span> <span class="btn-text">长按下载</span> </button> <button class="btn btn-danger btn-with-icon ripple-container long-press-btn" data-ripple-color="white" data-long-press-text="删除已确认!"> <span class="progress-indicator"> <div class="progress-bar"></div> </span> <span class="long-press-tooltip">长按确认删除</span> <span class="btn-icon">⚠️</span> <span class="btn-text">长按删除</span> </button> </div> <!-- 圆形按钮演示 --> <div class="demo-section"> <div class="section-title">⭕ 圆形浮动按钮</div> <div class="demo-info"> 经典的Material Design圆形浮动按钮 (FAB) </div> <button class="btn btn-circle ripple-container" data-ripple-color="white"> ➕ </button> <button class="btn btn-success btn-circle ripple-container" data-ripple-color="white"> ✓ </button> <button class="btn btn-warning btn-circle ripple-container" data-ripple-color="white"> ⚡ </button> <button class="btn btn-danger btn-circle ripple-container" data-ripple-color="white"> ❌ </button> </div> <!-- 卡片和列表演示 --> <div class="demo-section"> <div class="section-title">📋 交互式列表</div> <div class="demo-info"> 列表项支持水波纹效果,图标不会被遮挡 </div> <div class="card"> <div class="list-item ripple-container" data-ripple-color="dark"> <div class="list-icon">📧</div> <div class="list-content"> <div class="list-title">新邮件通知</div> <div class="list-subtitle">您有3封未读邮件</div> </div> </div> <div class="list-item ripple-container" data-ripple-color="dark"> <div class="list-icon">🔔</div> <div class="list-content"> <div class="list-title">系统通知</div> <div class="list-subtitle">软件更新可用</div> </div> </div> <div class="list-item ripple-container" data-ripple-color="dark"> <div class="list-icon">👤</div> <div class="list-content"> <div class="list-title">用户设置</div> <div class="list-subtitle">管理您的个人资料</div> </div> </div> </div> </div> <!-- 输入框和开关演示 --> <div class="demo-section"> <div class="section-title">🎛️ 交互控件</div> <div class="demo-info"> 输入框和开关也支持水波纹反馈效果 </div> <div class="input-container"> <input type="text" class="input-field ripple-container" placeholder="点击输入框查看效果" data-ripple-color="blue"> </div> <div class="switch-container"> <span>启用通知</span> <div class="switch ripple-container" data-ripple-color="blue"> <div class="switch-thumb"></div> </div> </div> <div class="switch-container"> <span>深色模式</span> <div class="switch ripple-container" data-ripple-color="blue"> <div class="switch-thumb"></div> </div> </div> </div> </div> <script> class MaterialRipple { constructor() { this.longPressTimer = null; this.longPressDelay = 1000; // 长按延迟时间 this.init(); } init() { // 为所有水波纹容器添加事件监听 document.addEventListener('click', this.handleClick.bind(this)); document.addEventListener('mousedown', this.handleMouseDown.bind(this)); document.addEventListener('mouseup', this.handleMouseUp.bind(this)); document.addEventListener('mouseleave', this.handleMouseLeave.bind(this)); // 触摸事件支持 document.addEventListener('touchstart', this.handleTouchStart.bind(this)); document.addEventListener('touchend', this.handleTouchEnd.bind(this)); document.addEventListener('touchcancel', this.handleTouchCancel.bind(this)); // 开关特殊处理 this.initSwitches(); } // 处理点击事件 handleClick(e) { const container = e.target.closest('.ripple-container'); if (!container) return; // 开关特殊处理 if (container.classList.contains('switch')) { this.toggleSwitch(container); } this.createRipple(e, container); } // 处理鼠标按下 handleMouseDown(e) { const container = e.target.closest('.long-press-btn'); if (!container) return; this.startLongPress(e, container); } // 处理鼠标释放 handleMouseUp(e) { this.endLongPress(); } // 处理鼠标离开 handleMouseLeave(e) { this.endLongPress(); } // 处理触摸开始 handleTouchStart(e) { const container = e.target.closest('.long-press-btn'); if (!container) return; // 创建模拟的鼠标事件对象 const touch = e.touches[0]; const mouseEvent = { clientX: touch.clientX, clientY: touch.clientY, target: e.target }; this.startLongPress(mouseEvent, container); } // 处理触摸结束 handleTouchEnd(e) { this.endLongPress(); } // 处理触摸取消 handleTouchCancel(e) { this.endLongPress(); } // 创建水波纹效果 createRipple(e, container, isLongPress = false) { const rect = container.getBoundingClientRect(); const size = Math.max(rect.width, rect.height); const x = e.clientX - rect.left - size / 2; const y = e.clientY - rect.top - size / 2; const ripple = document.createElement('div'); ripple.className = isLongPress ? 'ripple-long-press' : 'ripple'; // 设置水波纹颜色 const rippleColor = container.dataset.rippleColor; if (rippleColor) { ripple.classList.add(`ripple-${rippleColor}`); } ripple.style.width = ripple.style.height = size + 'px'; ripple.style.left = x + 'px'; ripple.style.top = y + 'px'; // 如果是图标按钮,确保水波纹不遮挡图标 if (container.classList.contains('btn-with-icon')) { const icon = container.querySelector('.btn-icon'); if (icon) { const iconRect = icon.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); const iconRelativeX = iconRect.left - containerRect.left; const iconRelativeY = iconRect.top - containerRect.top; // 调整水波纹起始位置,避开图标区域 if (e.clientX - rect.left < iconRelativeX + iconRect.width + 10) { ripple.style.left = (iconRelativeX + iconRect.width + 5) + 'px'; } } } container.appendChild(ripple); // 动画完成后移除水波纹元素 setTimeout(() => { if (ripple.parentNode) { ripple.parentNode.removeChild(ripple); } }, isLongPress ? 2000 : 600); } // 开始长按检测 startLongPress(e, container) { const tooltip = container.querySelector('.long-press-tooltip'); const progressIndicator = container.querySelector('.progress-indicator'); const progressBar = container.querySelector('.progress-bar'); // 显示提示和进度条 if (tooltip) tooltip.classList.add('show'); if (progressIndicator) progressIndicator.classList.add('show'); // 重置进度条 if (progressBar) { progressBar.style.transition = `width ${this.longPressDelay}ms linear`; progressBar.style.width = '100%'; } this.longPressTimer = setTimeout(() => { this.triggerLongPress(e, container); }, this.longPressDelay); } // 结束长按检测 endLongPress() { if (this.longPressTimer) { clearTimeout(this.longPressTimer); this.longPressTimer = null; } // 隐藏所有长按相关元素 document.querySelectorAll('.long-press-tooltip.show').forEach(tooltip => { tooltip.classList.remove('show'); }); document.querySelectorAll('.progress-indicator.show').forEach(indicator => { indicator.classList.remove('show'); const progressBar = indicator.querySelector('.progress-bar'); if (progressBar) { progressBar.style.transition = 'width 0.3s ease'; progressBar.style.width = '0%'; } }); } // 触发长按事件 triggerLongPress(e, container) { // 创建长按水波纹 this.createRipple(e, container, true); // 显示长按文字提示 const longPressText = container.dataset.longPressText; if (longPressText) { this.showToast(longPressText); } // 隐藏提示元素 this.endLongPress(); // 触发自定义长按事件 const longPressEvent = new CustomEvent('longpress', { detail: { element: container, originalEvent: e } }); container.dispatchEvent(longPressEvent); } // 初始化开关 initSwitches() { document.querySelectorAll('.switch').forEach(switchEl => { switchEl.addEventListener('longpress', (e) => { console.log('开关长按事件触发'); }); }); } // 切换开关状态 toggleSwitch(switchEl) { switchEl.classList.toggle('active'); } // 显示消息提示 showToast(message) { // 移除已存在的提示 const existingToast = document.querySelector('.toast'); if (existingToast) { existingToast.remove(); } const toast = document.createElement('div'); toast.className = 'toast'; toast.textContent = message; toast.style.cssText = ` position: fixed; top: 20px; right: 20px; background: rgba(0, 0, 0, 0.8); color: white; padding: 15px 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); transform: translateX(400px); transition: transform 0.3s ease; z-index: 10000; font-size: 14px; max-width: 300px; `; document.body.appendChild(toast); // 显示动画 setTimeout(() => { toast.style.transform = 'translateX(0)'; }, 100); // 自动隐藏 setTimeout(() => { toast.style.transform = 'translateX(400px)'; setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } }, 300); }, 3000); } } // 自定义事件监听示例 document.addEventListener('longpress', (e) => { console.log('长按事件触发:', e.detail.element); // 可以根据不同按钮执行不同操作 const element = e.detail.element; if (element.textContent.includes('下载')) { console.log('开始下载文件...'); } else if (element.textContent.includes('删除')) { console.log('确认删除操作...'); } }); // 页面加载完成后初始化 document.addEventListener('DOMContentLoaded', () => { new MaterialRipple(); // 添加一些演示交互 document.querySelectorAll('.list-item').forEach(item => { item.addEventListener('click', () => { const title = item.querySelector('.list-title').textContent; console.log(`点击了列表项: ${title}`); }); }); // 输入框聚焦效果 document.querySelectorAll('.input-field').forEach(input => { input.addEventListener('focus', () => { input.parentElement.style.transform = 'scale(1.02)'; }); input.addEventListener('blur', () => { input.parentElement.style.transform = 'scale(1)'; }); }); }); // 键盘快捷键支持 document.addEventListener('keydown', (e) => { if (e.key === 'Enter' && e.target.classList.contains('ripple-container')) { // 模拟点击事件 const clickEvent = new MouseEvent('click', { clientX: e.target.offsetLeft + e.target.offsetWidth / 2, clientY: e.target.offsetTop + e.target.offsetHeight / 2, bubbles: true }); e.target.dispatchEvent(clickEvent); } }); </script> </body> </html>