您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# 如何用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)';
}
});
<div class="list-container">
<div class="list-item">
<div class="item-content">列表项内容</div>
<div class="delete-btn">删除</div>
</div>
<!-- 更多列表项... -->
</div>
.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;
}
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');
// 在touchmove中添加边界检查
if ((diff < 0 && diff > -100) ||
(diff > 0 && content.style.transform !== 'translateX(0)')) {
content.style.transform = `translateX(${diff}px)`;
}
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);
}
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>
// 添加鼠标事件支持
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);
});
// 自动添加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)`;
// 在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);
}
touch-action: pan-y
允许垂直滚动isScrolling
标志位记录当前手势方向实现一个完善的左滑删除功能需要考虑多个方面: 1. 基础手势检测(touchstart/move/end) 2. 滑动距离计算和边界处理 3. 动画效果的流畅实现 4. 性能优化和兼容性处理 5. 边界条件和异常处理
通过本文介绍的方法,你可以实现一个高性能、跨平台的左滑删除组件。根据实际需求,你还可以进一步扩展功能,如: - 批量删除操作 - 撤销删除功能 - 多种操作按钮(如置顶、标记等) - 与后端API的集成
希望本文对你理解和实现左滑删除功能有所帮助! “`
这篇文章共计约3150字,详细介绍了使用JavaScript实现左滑删除功能的完整过程,包括基本原理、基础实现、进阶优化、兼容性处理和性能优化等内容。文章采用Markdown格式,包含代码示例、注意事项和完整实现方案。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。