在Svelte项目中实现滑动操作时,经常会遇到用户滑动超出容器预设边界的问题,比如横向滑动的轮播组件滑出可视区域,或者纵向滚动的内容区域滑到顶部或底部后还能继续拖动,这类问题会破坏交互的流畅性。我们可以通过监听滑动相关的事件,结合偏移量计算来限制滑动范围,避免超出边界的情况发生。

核心实现思路
防止滑动超出边界的核心逻辑分为三步:首先记录滑动操作的起始位置,然后在滑动过程中实时计算当前的偏移量,最后判断偏移量是否超出预设的边界范围,若超出则强制将偏移量限制在边界值内。
需要监听的事件
- 触摸设备:
touchstart、touchmove、touchend - 鼠标设备:
mousedown、mousemove、mouseup
完整实现示例
以下是一个横向滑动容器的完整Svelte组件示例,支持触摸和鼠标操作,限制滑动范围在0到最大偏移量之间:
<script>
import { onMount } from 'svelte';
// 容器可滑动的最大偏移量,根据内容宽度和容器宽度计算
let maxOffset = 0;
// 当前滑动偏移量
let offset = 0;
// 滑动起始位置
let startX = 0;
// 是否正在滑动
let isDragging = false;
// 容器元素引用
let containerRef;
// 内容元素引用
let contentRef;
// 计算最大偏移量
const calculateMaxOffset = () => {
if (containerRef && contentRef) {
const containerWidth = containerRef.clientWidth;
const contentWidth = contentRef.scrollWidth;
// 内容宽度小于容器宽度时,不需要滑动,最大偏移为0
maxOffset = Math.max(0, contentWidth - containerWidth);
}
};
// 处理滑动开始
const handleStart = (clientX) => {
isDragging = true;
startX = clientX;
};
// 处理滑动移动
const handleMove = (clientX) => {
if (!isDragging) return;
// 计算当前滑动的偏移差
const deltaX = clientX - startX;
// 临时计算新的偏移量
let newOffset = offset - deltaX;
// 限制偏移量在0到maxOffset之间
if (newOffset < 0) {
newOffset = 0;
} else if (newOffset > maxOffset) {
newOffset = maxOffset;
}
// 更新偏移量
offset = newOffset;
// 更新起始位置,避免下次计算差值过大
startX = clientX;
};
// 处理滑动结束
const handleEnd = () => {
isDragging = false;
};
// 触摸事件处理
const onTouchStart = (e) => {
handleStart(e.touches[0].clientX);
};
const onTouchMove = (e) => {
e.preventDefault(); // 阻止默认滚动行为
handleMove(e.touches[0].clientX);
};
const onTouchEnd = () => {
handleEnd();
};
// 鼠标事件处理
const onMouseDown = (e) => {
e.preventDefault();
handleStart(e.clientX);
};
const onMouseMove = (e) => {
if (!isDragging) return;
handleMove(e.clientX);
};
const onMouseUp = () => {
handleEnd();
};
// 组件挂载后计算最大偏移量,同时监听窗口 resize 重新计算
onMount(() => {
calculateMaxOffset();
const resizeObserver = new ResizeObserver(() => {
calculateMaxOffset();
});
resizeObserver.observe(containerRef);
resizeObserver.observe(contentRef);
// 全局监听鼠标移动和抬起事件,避免鼠标拖出容器后丢失事件
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
return () => {
resizeObserver.disconnect();
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
};
});
</script>
<div
class="slider-container"
bind:this={containerRef}
on:touchstart={onTouchStart}
on:touchmove={onTouchMove}
on:touchend={onTouchEnd}
on:mousedown={onMouseDown}
>
<div
class="slider-content"
bind:this={contentRef}
style="transform: translateX(-{offset}px)"
>
<div class="slider-item">内容1</div>
<div class="slider-item">内容2</div>
<div class="slider-item">内容3</div>
<div class="slider-item">内容4</div>
<div class="slider-item">内容5</div>
</div>
</div>
<style>
.slider-container {
width: 100%;
overflow: hidden;
user-select: none;
}
.slider-content {
display: flex;
transition: transform 0.1s ease-out;
will-change: transform;
}
.slider-item {
flex-shrink: 0;
width: 200px;
height: 150px;
margin-right: 16px;
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
border-radius: 8px;
}
</style>
关键点说明
上述示例中,我们通过maxOffset定义了滑动的上限,即内容宽度减去容器宽度的差值,保证内容不会被滑出可视区域。在handleMove函数中,每次计算新的偏移量后都会进行边界判断,若小于0则强制设为0,若大于maxOffset则强制设为maxOffset,从逻辑上杜绝了超出边界的可能。
另外,我们同时监听了触摸和鼠标事件,覆盖了移动端和桌面端的不同操作场景,同时使用ResizeObserver监听容器和内容尺寸变化,动态更新最大偏移量,适配窗口大小变化或者内容动态更新的场景。
纵向滑动适配
如果是纵向滑动的场景,只需要将X轴相关的计算替换为Y轴即可:把clientX换成clientY,偏移量计算使用translateY,最大偏移量计算使用内容高度减去容器高度,逻辑和横向滑动完全一致。