如何用javascript实现左滑删除

发布时间:2021-10-15 10:45:20 作者:iii
来源:亿速云 阅读:162
# 如何用JavaScript实现左滑删除

## 目录
1. [前言](#前言)
2. [基本原理](#基本原理)
3. [基础实现](#基础实现)
4. [进阶优化](#进阶优化)
5. [完整代码示例](#完整代码示例)
6. [兼容性处理](#兼容性处理)
7. [性能优化](#性能优化)
8. [常见问题](#常见问题)
9. [总结](#总结)

## 前言

在移动端Web开发中,"左滑删除"已成为列表项交互的标准模式。这种手势操作最早由苹果在iOS邮件应用中推广,现已广泛应用于各类移动应用。本文将详细介绍如何使用原生JavaScript实现这一功能,包括基础实现、手势处理、动画效果和性能优化等内容。

## 基本原理

左滑删除的核心原理是通过监听触摸事件(touchstart/touchmove/touchend)来实现:

1. **触摸开始**:记录初始触摸位置
2. **触摸移动**:计算滑动距离,实时更新元素位置
3. **触摸结束**:根据滑动距离决定是否触发删除操作

```javascript
const item = document.querySelector('.list-item');
let startX, currentX;

item.addEventListener('touchstart', (e) => {
  startX = e.touches[0].clientX;
});

item.addEventListener('touchmove', (e) => {
  currentX = e.touches[0].clientX;
  const diff = currentX - startX;
  // 控制最大滑动距离
  if (diff < 0 && diff > -100) {
    item.style.transform = `translateX(${diff}px)`;
  }
});

item.addEventListener('touchend', () => {
  if (currentX - startX < -50) {
    // 触发删除
  } else {
    // 恢复原位
    item.style.transform = 'translateX(0)';
  }
});

基础实现

HTML结构

<div class="list-container">
  <div class="list-item">
    <div class="item-content">列表项内容</div>
    <div class="delete-btn">删除</div>
  </div>
  <!-- 更多列表项... -->
</div>

CSS样式

.list-item {
  position: relative;
  width: 100%;
  height: 60px;
  overflow: hidden;
  transition: transform 0.3s ease;
}

.item-content {
  position: absolute;
  width: 100%;
  height: 100%;
  background: #fff;
  z-index: 1;
}

.delete-btn {
  position: absolute;
  right: 0;
  width: 80px;
  height: 100%;
  background: red;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 0;
}

JavaScript实现

class SwipeToDelete {
  constructor(container) {
    this.container = document.querySelector(container);
    this.items = this.container.querySelectorAll('.list-item');
    this.init();
  }

  init() {
    this.items.forEach(item => {
      const content = item.querySelector('.item-content');
      const deleteBtn = item.querySelector('.delete-btn');
      
      let startX, currentX;
      
      content.addEventListener('touchstart', (e) => {
        startX = e.touches[0].clientX;
      }, { passive: true });
      
      content.addEventListener('touchmove', (e) => {
        currentX = e.touches[0].clientX;
        const diff = currentX - startX;
        
        if (diff < 0 && diff > -100) {
          content.style.transform = `translateX(${diff}px)`;
        }
      }, { passive: true });
      
      content.addEventListener('touchend', () => {
        if (currentX - startX < -50) {
          content.style.transform = 'translateX(-80px)';
        } else {
          content.style.transform = 'translateX(0)';
        }
      });
      
      deleteBtn.addEventListener('click', () => {
        item.classList.add('removing');
        setTimeout(() => {
          item.remove();
        }, 300);
      });
    });
  }
}

new SwipeToDelete('.list-container');

进阶优化

1. 边界条件处理

// 在touchmove中添加边界检查
if ((diff < 0 && diff > -100) || 
    (diff > 0 && content.style.transform !== 'translateX(0)')) {
  content.style.transform = `translateX(${diff}px)`;
}

2. 惯性滑动效果

let velocity = 0;
let lastX = 0;
let lastTime = 0;

// 在touchmove中计算速度
const now = Date.now();
const timeDiff = now - lastTime;
if (timeDiff > 0) {
  velocity = (currentX - lastX) / timeDiff;
  lastX = currentX;
  lastTime = now;
}

// 在touchend中应用惯性
if (Math.abs(velocity) > 0.5) {
  const distance = velocity * 100; // 惯性滑动距离
  const targetX = parseFloat(content.style.transform.replace('translateX(', '').replace('px)', '')) + distance;
  
  // 限制在0到-80px之间
  const finalX = Math.max(-80, Math.min(0, targetX));
  content.style.transition = 'transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1)';
  content.style.transform = `translateX(${finalX}px)`;
  
  setTimeout(() => {
    content.style.transition = '';
  }, 300);
}

3. 多手势冲突处理

let isScrolling;

content.addEventListener('touchmove', (e) => {
  // 判断是垂直滚动还是水平滑动
  const yDiff = Math.abs(e.touches[0].clientY - startY);
  const xDiff = Math.abs(e.touches[0].clientX - startX);
  
  if (isScrolling === undefined) {
    isScrolling = yDiff > xDiff;
  }
  
  if (!isScrolling) {
    e.preventDefault();
    // 处理滑动逻辑...
  }
}, { passive: false });

完整代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <title>左滑删除示例</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
      background: #f5f5f5;
    }
    .list-container {
      max-width: 500px;
      margin: 20px auto;
    }
    .list-item {
      position: relative;
      width: 100%;
      height: 60px;
      margin-bottom: 10px;
      overflow: hidden;
      border-radius: 8px;
      box-shadow: 0 1px 3px rgba(0,0,0,0.1);
    }
    .item-content {
      position: absolute;
      width: 100%;
      height: 100%;
      background: #fff;
      padding: 10px 15px;
      display: flex;
      align-items: center;
      z-index: 1;
      transition: transform 0.2s ease-out;
      touch-action: pan-y;
    }
    .delete-btn {
      position: absolute;
      right: 0;
      width: 80px;
      height: 100%;
      background: #ff3b30;
      color: white;
      display: flex;
      align-items: center;
      justify-content: center;
      z-index: 0;
      font-size: 14px;
    }
    .removing {
      transform: translateX(-100%);
      opacity: 0;
      transition: all 0.3s ease;
    }
  </style>
</head>
<body>
  <div class="list-container">
    <div class="list-item">
      <div class="item-content">左滑可以删除此项</div>
      <div class="delete-btn">删除</div>
    </div>
    <!-- 更多列表项... -->
  </div>

  <script>
    class SwipeToDelete {
      constructor(container) {
        this.container = document.querySelector(container);
        this.items = this.container.querySelectorAll('.list-item');
        this.init();
      }
      
      init() {
        this.items.forEach(item => {
          const content = item.querySelector('.item-content');
          const deleteBtn = item.querySelector('.delete-btn');
          
          let startX, startY, currentX;
          let isScrolling;
          let velocity = 0;
          let lastX = 0;
          let lastTime = 0;
          
          content.addEventListener('touchstart', (e) => {
            startX = e.touches[0].clientX;
            startY = e.touches[0].clientY;
            lastX = startX;
            lastTime = Date.now();
            isScrolling = undefined;
            content.style.transition = 'none';
          }, { passive: true });
          
          content.addEventListener('touchmove', (e) => {
            currentX = e.touches[0].clientX;
            const currentY = e.touches[0].clientY;
            
            if (isScrolling === undefined) {
              isScrolling = Math.abs(currentY - startY) > Math.abs(currentX - startX);
            }
            
            if (!isScrolling) {
              e.preventDefault();
              const diff = currentX - startX;
              const now = Date.now();
              const timeDiff = now - lastTime;
              
              if (timeDiff > 0) {
                velocity = (currentX - lastX) / timeDiff;
                lastX = currentX;
                lastTime = now;
              }
              
              if ((diff < 0 && diff > -100) || 
                  (diff > 0 && content.style.transform !== 'translateX(0)')) {
                content.style.transform = `translateX(${diff}px)`;
              }
            }
          }, { passive: false });
          
          content.addEventListener('touchend', () => {
            const currentTransform = content.style.transform;
            const currentTranslateX = currentTransform ? 
              parseFloat(currentTransform.replace('translateX(', '').replace('px)', '')) : 0;
            
            let targetX = 0;
            
            if (Math.abs(velocity) > 0.5) {
              const distance = velocity * 100;
              targetX = currentTranslateX + distance;
            } else if (currentTranslateX < -30) {
              targetX = -80;
            }
            
            // 限制在0到-80px之间
            targetX = Math.max(-80, Math.min(0, targetX));
            
            content.style.transition = 'transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1)';
            content.style.transform = `translateX(${targetX}px)`;
            
            setTimeout(() => {
              content.style.transition = '';
            }, 300);
          });
          
          deleteBtn.addEventListener('click', () => {
            item.classList.add('removing');
            setTimeout(() => {
              item.remove();
            }, 300);
          });
        });
      }
    }
    
    // 初始化
    document.addEventListener('DOMContentLoaded', () => {
      new SwipeToDelete('.list-container');
      
      // 添加示例数据
      const container = document.querySelector('.list-container');
      for (let i = 0; i < 10; i++) {
        const item = document.createElement('div');
        item.className = 'list-item';
        item.innerHTML = `
          <div class="item-content">列表项 ${i + 1}</div>
          <div class="delete-btn">删除</div>
        `;
        container.appendChild(item);
      }
      
      // 重新初始化以包含新项目
      new SwipeToDelete('.list-container');
    });
  </script>
</body>
</html>

兼容性处理

1. 桌面端兼容

// 添加鼠标事件支持
content.addEventListener('mousedown', (e) => {
  if (e.button !== 0) return; // 只处理左键
  startX = e.clientX;
  isMouseDown = true;
  content.style.transition = 'none';
});

document.addEventListener('mousemove', (e) => {
  if (!isMouseDown) return;
  currentX = e.clientX;
  const diff = currentX - startX;
  
  if ((diff < 0 && diff > -100) || 
      (diff > 0 && content.style.transform !== 'translateX(0)')) {
    content.style.transform = `translateX(${diff}px)`;
  }
});

document.addEventListener('mouseup', () => {
  if (!isMouseDown) return;
  isMouseDown = false;
  
  const currentTransform = content.style.transform;
  const currentTranslateX = currentTransform ? 
    parseFloat(currentTransform.replace('translateX(', '').replace('px)', '')) : 0;
  
  if (currentTranslateX < -30) {
    content.style.transition = 'transform 0.3s ease';
    content.style.transform = 'translateX(-80px)';
  } else {
    content.style.transition = 'transform 0.3s ease';
    content.style.transform = 'translateX(0)';
  }
  
  setTimeout(() => {
    content.style.transition = '';
  }, 300);
});

2. 浏览器前缀处理

// 自动添加CSS前缀
function prefixStyle(style) {
  const vendor = (() => {
    const vendors = ['webkit', 'moz', 'ms', 'o'];
    for (let i = 0; i < vendors.length; i++) {
      if (typeof document.body.style[vendors[i] + 'Transform'] !== 'undefined') {
        return vendors[i];
      }
    }
    return '';
  })();
  
  if (vendor === '') return style;
  return vendor + style.charAt(0).toUpperCase() + style.substr(1);
}

const transform = prefixStyle('transform');
content.style[transform] = `translateX(${diff}px)`;

性能优化

  1. 使用transform代替left/top:transform不会触发重排,性能更好
  2. 减少DOM操作:缓存DOM查询结果
  3. 使用passive事件监听器:提高滚动性能
  4. 合理使用requestAnimationFrame:优化动画性能
  5. 避免频繁的重绘:使用will-change属性提示浏览器
// 在CSS中添加
.item-content {
  will-change: transform;
}

// 使用requestAnimationFrame优化动画
function animateSlide(element, targetX) {
  let startTime;
  const duration = 300;
  const startX = parseFloat(element.style.transform.replace('translateX(', '').replace('px)', ''));
  
  function step(timestamp) {
    if (!startTime) startTime = timestamp;
    const progress = Math.min((timestamp - startTime) / duration, 1);
    const easedProgress = 0.5 * (1 - Math.cos(Math.PI * progress));
    const currentX = startX + (targetX - startX) * easedProgress;
    
    element.style.transform = `translateX(${currentX}px)`;
    
    if (progress < 1) {
      requestAnimationFrame(step);
    }
  }
  
  requestAnimationFrame(step);
}

常见问题

1. 滑动不流畅

2. 与其他手势冲突

3. 点击穿透问题

4. 列表项高度塌陷

总结

实现一个完善的左滑删除功能需要考虑多个方面: 1. 基础手势检测(touchstart/move/end) 2. 滑动距离计算和边界处理 3. 动画效果的流畅实现 4. 性能优化和兼容性处理 5. 边界条件和异常处理

通过本文介绍的方法,你可以实现一个高性能、跨平台的左滑删除组件。根据实际需求,你还可以进一步扩展功能,如: - 批量删除操作 - 撤销删除功能 - 多种操作按钮(如置顶、标记等) - 与后端API的集成

希望本文对你理解和实现左滑删除功能有所帮助! “`

这篇文章共计约3150字,详细介绍了使用JavaScript实现左滑删除功能的完整过程,包括基本原理、基础实现、进阶优化、兼容性处理和性能优化等内容。文章采用Markdown格式,包含代码示例、注意事项和完整实现方案。

推荐阅读:
  1. 如使用JavaScript实现抖音罗盘时钟
  2. 小程序怎么实现左滑删除效果

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

javascript

上一篇:PHP中怎么创建临时文件

下一篇:javascript中如何使用split

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》