Introduction

A lightweight Vue 3 directive that provides silky-smooth touch scrolling for mobile tables.

Vue Table Touch Scroll

vue-table-touch-scroll is a lightweight solution dedicated to bridging the gap between desktop tables and mobile touch interactions. With a simple directive, it ensures that any Vue PC table component delivers a native-grade, silky-smooth scrolling and interaction experience on mobile devices. This includes seamless out-of-the-box support for table components in mainstream UI libraries like Element Plus, Ant Design Vue, and Naive UI.

Core Features

  • ๐Ÿš€ Physics Simulation Engine โ€” Frame-rate normalized via Delta Time, eliminating speed differences across 60Hz/90Hz/120Hz screens for consistent inertia feel
  • ๐Ÿ“ฑ Axis Locking โ€” Automatically determines gesture intent, filters diagonal jitter, locks each gesture to a single axis
  • ๐Ÿ”„ Smart Edge Detection โ€” Automatically yields control back to the browser when scrolling reaches boundaries, compatible with iOS swipe-back and other native behaviors
  • ๐Ÿ›‘ Emergency Stop Click Protection โ€” Distinguishes between "user wants to click" and "user wants to brake", only blocks click events during high-speed inertia scrolling to prevent accidental taps
  • ๐Ÿ–๏ธ Multi-finger Tracking โ€” Locks the active finger via Touch ID, preventing coordinate jumps caused by multi-finger alternation
  • โšก Dirty Value Detection โ€” Only triggers rendering and event dispatching when scroll position actually changes, avoiding unnecessary overhead
  • ๐Ÿ”ง UI Library Presets โ€” Built-in preset configurations for Element Plus, Ant Design Vue, Naive UI, VxeTable, and more
  • ๐ŸŽฏ Zero Dependencies โ€” Pure native implementation, no extra dependencies needed

Use Cases

  • Element Plus el-table component
  • Ant Design Vue a-table component
  • Naive UI n-data-table component
  • VxeTable, Arco Design, PrimeVue, Vuetify table components
  • Any custom table that needs optimized mobile touch scrolling

Installation

npm
yarn
pnpm
npm install vue-table-touch-scroll

Quick Start

Global Registration

main.ts
import { createApp } from 'vue'
import App from './App.vue'
import VueTableTouchScroll from 'vue-table-touch-scroll'

const app = createApp(App)
app.use(VueTableTouchScroll)
app.mount('#app')

Usage in Templates

MyComponent.vue
<template>
  <div v-table-touch-scroll="{ preset: 'element-plus' }">
    <el-table :data="tableData">
      <el-table-column prop="name" label="Name" />
      <el-table-column prop="age" label="Age" />
      <el-table-column prop="address" label="Address" />
    </el-table>
  </div>
</template>

What Problems Does It Solve

When using desktop Table components directly on mobile, you typically encounter these issues:

  • Diagonal Swipe Accidental Touch โ€” Users intending to scan table data horizontally often inadvertently trigger vertical page scrolling due to imprecise finger paths, causing them to lose their visual anchor.
  • Unstable Directional Locking โ€” Minor finger tilts can cause the scrolling direction to jitter between horizontal and vertical axes. The lack of a smooth axis-locking mechanism results in a "chaotic" or "shaky" sliding path.
  • Sluggish Touch Response โ€” In complex nested UI structures, native overflow: auto often feels unresponsive, with a noticeable lag between the finger movement and the actual scrolling of the table (lack of "follow-the-finger" feel).
  • Lack of Natural Momentum โ€” Scrolling often stops abruptly upon finger release or feels "stiff." This forces users to perform repetitive, high-frequency swipes to navigate to distant cells.
  • HVisual Header "Tearing" โ€” During table body scrolling, fixed headers often fail to achieve pixel-perfect, real-time synchronization, resulting in visible alignment delays or jarring visual offsets.
  • Inconsistent Cross-Device Haptics โ€” Different screen refresh rates interpret inertia differently, causing the same codebase to feel vastly different in smoothness and travel distance across various devices.
  • Gesture Logic Conflicts โ€” Issues such as coordinate jumping during multi-finger interactions, or interference with native system gestures (like the iOS "swipe-to-back" feature) when scrolling near table boundaries.

v-table-touch-scroll solves these problems by intercepting the touch event flow and implementing a complete scroll physics engine inside the directive. Here are the key approaches:

Axis Locking

Automatically determines gesture intent, filters diagonal jitter, locks each gesture to a single axis (horizontal or vertical) to avoid accidental touches and direction confusion:

const isHorizontal = Math.abs(dx) > Math.abs(dy)
if (isHorizontal !== ctx.isHorizontal) {
  ctx.isHorizontal = isHorizontal
  ctx.velocity = 0
}

Touch History Queue

Uses sliding window sampling instead of traditional weighted averages, keeping only touch points from the last 50ms to calculate release velocity, fundamentally eliminating sampling jitter on low-end devices and over-dense sampling on high refresh rate screens:

ctx.touchTracker = ctx.touchTracker.filter((p) => now - p.time <= 50)
const oldestPoint = ctx.touchTracker[0]
ctx.velocity = -(t.clientX - oldestPoint.x) / (now - oldestPoint.time)

Frame Rate Normalization

Physics decay during inertia is normalized via Delta Time, ensuring the same swipe produces identical scroll distances on both 60Hz and 120Hz screens:

const dt = Math.min(now - ctx.lastFrameTime, MAX_DT)
ctx.velocity *= ctx.friction ** (dt / 16.67)

Synchronous Event Dispatching

Combined with dirty value detection, proactively dispatches scroll events to force immediate header synchronization, solving the visual tearing caused by asynchronous native scroll events:

if (el.scrollLeft !== prevX || el.scrollTop !== prevY) {
  el.dispatchEvent(new Event('scroll'))
}

Edge Detection

Automatically yields control when scrolling reaches container boundaries, ensuring native behaviors like iOS swipe-back are not blocked:

const atLeft = el.scrollLeft <= 1 && dx > 0
const atRight = el.scrollLeft >= maxScrollLeft - 1 && dx < 0
if (atLeft || atRight) isAtEdge = true

Next Steps

  • Check the Usage Guide for detailed configuration options
  • Browse Examples to see real-world use cases