当前位置: 首页 > news >正文

点击事件的防抖和节流

按钮防抖与节流解决方案

针对按钮频繁点击导致数字运算错误的问题,我设计了一个可视化解决方案,展示防抖(debounce)和节流(throttle)两种技术及其效果对比。

下面是完整的实现代码:

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>按钮防抖与节流解决方案</title><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"><script src="https://cdn.jsdelivr.net/npm/vue@3.2.45/dist/vue.global.js"></script><style>* {margin: 0;padding: 0;box-sizing: border-box;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;}body {background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);color: #fff;min-height: 100vh;padding: 20px;display: flex;justify-content: center;align-items: center;}.container {width: 100%;max-width: 1200px;margin: 0 auto;}header {text-align: center;margin-bottom: 40px;padding: 20px;}h1 {font-size: 2.8rem;margin-bottom: 10px;text-shadow: 0 2px 10px rgba(0,0,0,0.3);}.subtitle {font-size: 1.2rem;opacity: 0.9;max-width: 800px;margin: 0 auto;line-height: 1.6;}.content {display: grid;grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));gap: 30px;margin-bottom: 40px;}.card {background: rgba(255, 255, 255, 0.08);border-radius: 15px;padding: 30px;box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);backdrop-filter: blur(10px);border: 1px solid rgba(255, 255, 255, 0.1);transition: transform 0.3s ease, box-shadow 0.3s ease;}.card:hover {transform: translateY(-10px);box-shadow: 0 15px 35px rgba(0, 0, 0, 0.4);}.card-header {display: flex;align-items: center;margin-bottom: 25px;}.card-icon {width: 60px;height: 60px;border-radius: 50%;display: flex;align-items: center;justify-content: center;margin-right: 20px;font-size: 28px;}.card-1 .card-icon {background: linear-gradient(135deg, #00b09b, #96c93d);}.card-2 .card-icon {background: linear-gradient(135deg, #2193b0, #6dd5ed);}.card-3 .card-icon {background: linear-gradient(135deg, #ff5e62, #ff9966);}.card-title {font-size: 1.8rem;margin-bottom: 5px;}.card-description {opacity: 0.8;line-height: 1.6;margin-bottom: 25px;}.counter {font-size: 5rem;text-align: center;margin: 25px 0;font-weight: bold;text-shadow: 0 0 20px rgba(255, 255, 255, 0.3);}.btn {display: block;width: 100%;padding: 18px;font-size: 1.2rem;border: none;border-radius: 10px;background: linear-gradient(135deg, #ff8a00, #da1b60);color: white;cursor: pointer;font-weight: bold;letter-spacing: 1px;transition: all 0.3s ease;margin-top: 20px;box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);}.btn:hover {transform: translateY(-3px);box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);}.btn:active {transform: translateY(1px);}.btn:disabled {background: linear-gradient(135deg, #777, #555);transform: none;cursor: not-allowed;opacity: 0.7;}.status {text-align: center;padding: 15px;border-radius: 8px;margin-top: 20px;font-weight: bold;background: rgba(255, 255, 255, 0.1);}.execution-log {background: rgba(0, 0, 0, 0.2);border-radius: 10px;padding: 15px;height: 150px;overflow-y: auto;margin-top: 20px;font-size: 0.9rem;}.execution-log p {padding: 8px;border-bottom: 1px solid rgba(255, 255, 255, 0.1);margin-bottom: 5px;}.execution-log p:last-child {border-bottom: none;}.comparison-table {background: rgba(255, 255, 255, 0.08);border-radius: 15px;padding: 30px;box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);backdrop-filter: blur(10px);border: 1px solid rgba(255, 255, 255, 0.1);margin-top: 30px;}table {width: 100%;border-collapse: collapse;}th, td {padding: 15px;text-align: left;border-bottom: 1px solid rgba(255, 255, 255, 0.1);}th {font-size: 1.2rem;}tbody tr:hover {background: rgba(255, 255, 255, 0.05);}.tech-details {margin-top: 40px;padding: 30px;background: rgba(0, 0, 0, 0.2);border-radius: 15px;}.tech-title {font-size: 1.8rem;margin-bottom: 20px;text-align: center;}.tech-content {display: flex;gap: 30px;flex-wrap: wrap;}.tech-card {flex: 1;min-width: 300px;background: rgba(255, 255, 255, 0.08);border-radius: 10px;padding: 20px;}.tech-card h3 {font-size: 1.5rem;margin-bottom: 15px;color: #ff8a00;}.tech-card ul {padding-left: 20px;}.tech-card li {margin-bottom: 10px;line-height: 1.6;}.code-block {background: #1e1e1e;padding: 20px;border-radius: 8px;margin-top: 15px;overflow-x: auto;font-family: 'Courier New', monospace;}.footer {text-align: center;padding: 30px;margin-top: 40px;opacity: 0.8;font-size: 0.9rem;}@media (max-width: 768px) {.content {grid-template-columns: 1fr;}h1 {font-size: 2.2rem;}}</style>
</head>
<body><div id="app" class="container"><header><h1><i class="fas fa-hand-pointer"></i> 按钮防抖与节流解决方案</h1><p class="subtitle">当用户快速点击按钮时,数字运算会出现错误。本页面演示了如何使用防抖(debounce)和节流(throttle)技术来解决这个问题,确保您的应用程序在用户快速交互时仍能保持稳定和准确。</p></header><div class="content"><!-- 问题演示卡片 --><div class="card card-1"><div class="card-header"><div class="card-icon"><i class="fas fa-bug"></i></div><div><h2 class="card-title">问题演示</h2><p>快速点击按钮查看问题</p></div></div><p class="card-description">当用户快速点击按钮时,数字运算逻辑会出现问题。在这个例子中,每次点击应该增加1,但快速点击会导致计数错误。</p><div class="counter">{{ problemCounter }}</div><button class="btn" @click="handleProblemClick"><i class="fas fa-plus-circle"></i> 增加计数</button><div class="status" :style="{background: problemStatusColor}">{{ problemStatus }}</div><div class="execution-log"><p v-for="(log, index) in problemLogs" :key="'problem-log-'+index"><i class="fas fa-circle" style="color: #ff5e62; font-size: 8px;"></i> {{ log }}</p></div></div><!-- 防抖解决方案卡片 --><div class="card card-2"><div class="card-header"><div class="card-icon"><i class="fas fa-shield-alt"></i></div><div><h2 class="card-title">防抖解决方案</h2><p>仅在最后一次点击后执行</p></div></div><p class="card-description">防抖技术确保函数在用户停止点击一段时间后才执行。这可以防止快速连续点击导致的多次执行。</p><div class="counter">{{ debounceCounter }}</div><button class="btn" @click="handleDebounceClick"><i class="fas fa-filter"></i> 防抖增加</button><div class="status" :style="{background: debounceStatusColor}">{{ debounceStatus }}</div><div class="execution-log"><p v-for="(log, index) in debounceLogs" :key="'debounce-log-'+index"><i class="fas fa-circle" style="color: #6dd5ed; font-size: 8px;"></i> {{ log }}</p></div></div><!-- 节流解决方案卡片 --><div class="card card-3"><div class="card-header"><div class="card-icon"><i class="fas fa-tachometer-alt"></i></div><div><h2 class="card-title">节流解决方案</h2><p>按固定时间间隔执行</p></div></div><p class="card-description">节流技术确保函数在一定时间间隔内只执行一次。无论用户点击多快,函数都会按固定频率执行。</p><div class="counter">{{ throttleCounter }}</div><button class="btn" @click="handleThrottleClick"><i class="fas fa-hourglass-half"></i> 节流增加</button><div class="status" :style="{background: throttleStatusColor}">{{ throttleStatus }}</div><div class="execution-log"><p v-for="(log, index) in throttleLogs" :key="'throttle-log-'+index"><i class="fas fa-circle" style="color: #96c93d; font-size: 8px;"></i> {{ log }}</p></div></div></div><div class="comparison-table"><h2 style="text-align: center; margin-bottom: 25px;">技术对比</h2><table><thead><tr><th>技术</th><th>工作原理</th><th>适用场景</th><th>效果</th></tr></thead><tbody><tr><td><strong>防抖(Debounce)</strong></td><td>在事件触发后等待一段时间,如果在这段时间内没有再次触发事件,才执行函数</td><td>搜索建议、窗口大小调整、输入验证</td><td>避免不必要的计算,只在用户停止操作后执行</td></tr><tr><td><strong>节流(Throttle)</strong></td><td>在指定时间间隔内,无论事件触发多少次,只执行一次函数</td><td>按钮点击、滚动事件、鼠标移动</td><td>确保函数按固定频率执行,避免过度调用</td></tr></tbody></table></div><div class="tech-details"><h2 class="tech-title">技术实现细节</h2><div class="tech-content"><div class="tech-card"><h3><i class="fas fa-code"></i> 防抖实现代码</h3><div class="code-block"><pre><code>function debounce(func, delay) {let timer = null;return function(...args) {// 清除之前的定时器clearTimeout(timer);// 设置新的定时器timer = setTimeout(() => {func.apply(this, args);}, delay);};
}// 使用示例
const debouncedFn = debounce(() => {// 执行逻辑
}, 300);</code></pre></div><ul><li>创建一个返回函数,该函数封装了防抖逻辑</li><li>每次调用时清除之前的定时器</li><li>设置新的定时器,在延迟后执行目标函数</li><li>适用于需要等待用户停止操作后再执行的场景</li></ul></div><div class="tech-card"><h3><i class="fas fa-code"></i> 节流实现代码</h3><div class="code-block"><pre><code>function throttle(func, limit) {let lastFunc;let lastRan;return function(...args) {if (!lastRan) {func.apply(this, args);lastRan = Date.now();} else {clearTimeout(lastFunc);lastFunc = setTimeout(() => {if ((Date.now() - lastRan) >= limit) {func.apply(this, args);lastRan = Date.now();}}, limit - (Date.now() - lastRan));}};
}// 使用示例
const throttledFn = throttle(() => {// 执行逻辑
}, 300);</code></pre></div><ul><li>记录上次执行的时间</li><li>在时间间隔内只允许执行一次</li><li>确保函数在指定时间间隔内最多执行一次</li><li>适用于需要限制执行频率的场景</li></ul></div></div></div><div class="footer"><p>Vue 3 + TypeScript 按钮防抖与节流解决方案 | 设计用于处理快速点击事件中的数字运算问题</p><p>在实际项目中,您可以根据具体需求选择防抖或节流技术,或结合使用</p></div></div><script>const { createApp, ref, computed } = VuecreateApp({setup() {// 问题演示部分const problemCounter = ref(0)const problemStatus = ref('等待点击...')const problemStatusColor = ref('rgba(255, 255, 255, 0.1)')const problemLogs = ref([])const handleProblemClick = () => {// 模拟一个稍微复杂的数字运算const currentValue = problemCounter.valueconst newValue = currentValue + 1// 模拟一个可能出错的运算// 在快速点击时,由于JavaScript事件循环机制,可能导致计算错误problemCounter.value = newValue// 记录日志const now = new Date()const time = `${now.getSeconds()}.${now.getMilliseconds()}`problemLogs.value.push(`点击 @ ${time}s | 计算: ${currentValue} + 1 = ${newValue}`)// 最多保留5条日志if (problemLogs.value.length > 5) {problemLogs.value.shift()}problemStatus.value = `执行计算: ${currentValue} + 1 = ${newValue}`problemStatusColor.value = 'rgba(255, 94, 98, 0.5)'// 1.5秒后重置状态setTimeout(() => {problemStatus.value = '等待点击...'problemStatusColor.value = 'rgba(255, 255, 255, 0.1)'}, 1500)}// 防抖部分const debounceCounter = ref(0)const debounceStatus = ref('等待点击...')const debounceStatusColor = ref('rgba(255, 255, 255, 0.1)')const debounceLogs = ref([])let debounceTimer = nullconst handleDebounceClick = () => {// 清除之前的定时器clearTimeout(debounceTimer)const now = new Date()const time = `${now.getSeconds()}.${now.getMilliseconds()}`debounceLogs.value.push(`点击 @ ${time}s | 等待执行`)// 最多保留5条日志if (debounceLogs.value.length > 5) {debounceLogs.value.shift()}debounceStatus.value = '等待用户停止点击...'debounceStatusColor.value = 'rgba(33, 147, 176, 0.5)'// 设置新的定时器debounceTimer = setTimeout(() => {const currentValue = debounceCounter.valueconst newValue = currentValue + 1debounceCounter.value = newValueconst now = new Date()const time = `${now.getSeconds()}.${now.getMilliseconds()}`debounceLogs.value.push(`执行 @ ${time}s | 计算: ${currentValue} + 1 = ${newValue}`)debounceStatus.value = `执行计算: ${currentValue} + 1 = ${newValue}`debounceStatusColor.value = 'rgba(109, 213, 237, 0.7)'// 1.5秒后重置状态setTimeout(() => {debounceStatus.value = '等待点击...'debounceStatusColor.value = 'rgba(255, 255, 255, 0.1)'}, 1500)}, 500) // 500ms防抖延迟}// 节流部分const throttleCounter = ref(0)const throttleStatus = ref('等待点击...')const throttleStatusColor = ref('rgba(255, 255, 255, 0.1)')const throttleLogs = ref([])let throttleLastRan = 0let throttleTimer = nullconst handleThrottleClick = () => {const now = Date.now()const nowTime = new Date()const time = `${nowTime.getSeconds()}.${nowTime.getMilliseconds()}`throttleLogs.value.push(`点击 @ ${time}s`)// 最多保留5条日志if (throttleLogs.value.length > 5) {throttleLogs.value.shift()}// 如果距离上次执行超过500ms,立即执行if (now - throttleLastRan >= 500) {executeThrottle()} else {throttleStatus.value = '等待时间间隔...'throttleStatusColor.value = 'rgba(150, 201, 61, 0.5)'// 设置定时器,确保在时间间隔结束时执行clearTimeout(throttleTimer)throttleTimer = setTimeout(() => {executeThrottle()}, 500 - (now - throttleLastRan))}}const executeThrottle = () => {const currentValue = throttleCounter.valueconst newValue = currentValue + 1throttleCounter.value = newValueconst nowTime = new Date()const time = `${nowTime.getSeconds()}.${nowTime.getMilliseconds()}`throttleLogs.value.push(`执行 @ ${time}s | 计算: ${currentValue} + 1 = ${newValue}`)throttleStatus.value = `执行计算: ${currentValue} + 1 = ${newValue}`throttleStatusColor.value = 'rgba(150, 201, 61, 0.7)'throttleLastRan = Date.now()// 1.5秒后重置状态setTimeout(() => {throttleStatus.value = '等待点击...'throttleStatusColor.value = 'rgba(255, 255, 255, 0.1)'}, 1500)}return {problemCounter,problemStatus,problemStatusColor,problemLogs,handleProblemClick,debounceCounter,debounceStatus,debounceStatusColor,debounceLogs,handleDebounceClick,throttleCounter,throttleStatus,throttleStatusColor,throttleLogs,handleThrottleClick}}}).mount('#app')</script>
</body>
</html>

功能说明

这个页面展示了三种处理按钮点击的方式:

  1. 问题演示:直接处理点击事件,无任何防护措施。快速点击会导致数字计算错误和状态混乱。

  2. 防抖解决方案

    • 使用防抖技术(debounce)确保在用户停止点击500ms后才执行
    • 适合需要等待用户完成操作后执行的场景
    • 避免不必要的计算和渲染
  3. 节流解决方案

    • 使用节流技术(throttle)确保每500ms最多执行一次
    • 适合需要限制执行频率的场景
    • 确保用户操作有反馈但不会过度执行
http://www.dtcms.com/a/302869.html

相关文章:

  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-42,(知识点:D触发器,D锁存器,工作原理,区别)
  • Java HashMap中的compute及相关方法详解:从基础到Kafka Stream应用
  • C++ 哈希算法、贪心算法
  • CLion 远程 Linux C++开发配置
  • 【数据结构】递归暴力美学:二叉树链式结构的深度解析(含源码)
  • 2025最新Mybatis-plus教程(三)
  • 国内使用git clone下载huggingface
  • 鱼皮项目简易版 RPC 框架开发(一)
  • 设计模式(十五)行为型:命令模式详解
  • 简单工厂模式 Simple Factory Pattern
  • Qt元类型系统(QMetaType)详解
  • 11、Docker Compose 配置Mysql主从(单虚拟机)
  • 树状数组的概念、结构及实现
  • 塔能科技物联运维平台及城市照明市场竞争力分析
  • 国产测试用例管理工具横向评测:DevOps时代如何选择最适合的协作平台?
  • window显示驱动开发—RecycleCreateCommandList
  • Angular 依赖注入
  • 网络 编程
  • 洛谷刷题7.28
  • 基于AFLFast的fuzz自动化漏洞挖掘(1)
  • 【HTTP】防XSS+SQL注入:自定义HttpMessageConverter过滤链深度解决方案
  • 【React Context API 优化与性能实践指南】
  • DBAPI 实现分页查询的两种方法
  • 阿里云Ubuntu 22.04 ssh隔一段时间自动断开的解决方法
  • 【力扣热题100】哈希——两数之和
  • 【mysql】—— mysql中的timestamp 和 datetime(6) 有什么区别,为什么有的地方不建议使用timestamp
  • 智能制造,从工厂建模,工艺建模,柔性制造,精益制造,生产管控,库存,质量等多方面讲述智能制造的落地方案。
  • 破解PCB制造痛点,盘古信息IMS MOM 铸就数字化标杆工厂
  • PL/SQL
  • 开疆智能ModbusRTU转Profinet网关连接西门子CP341配置案例