【组件库】Interactjs中文指南:拖拽、缩放、手势与高级交互入门

15 阅读26分钟

前情说明:由于主包发现官网interactjs.io打不开,只能找github上找到源代码版本中的docs,但是是英文版本,于是主包花大价钱充了一个chatgtp,将docs整理并翻译成了以下👇中文版本。 ChatGPT_Image_2026年5月4日_17_26_37.png

介绍

什么是 interact.js?

interact.js 是一个面向现代浏览器的 JavaScript 库,用于实现拖放、缩放调整以及多点触控手势。它的免费开源版本提供了强大的选项,例如惯性、吸附以及限制范围的修饰器。

这个库的目标是:在不同浏览器和设备之间一致地呈现指针输入数据,并提供便捷方式来模拟用户指针似乎移动到了它实际上并没有移动到的位置(例如吸附、惯性等)。

请注意,默认情况下,interact.js 不会帮你移动元素。如果你希望元素在拖拽过程中产生移动效果,需要在你自己的事件监听器中为元素添加样式。这样,你可以完全控制发生的一切。

🌟 如果你希望开箱即用地获得交互反馈,可以看看 interact.js Pro。它内置了硬件加速反馈、列表重新排序、弹簧物理效果、Vue 和 React 组件等功能。

获取 Pro

快速开始

安装这个库之后,设置目标元素和交互的基本步骤如下:

  1. 创建一个 Interactable 目标。
  2. 配置它以启用动作,并添加修饰器惯性等。
  3. 添加事件监听器,用于提供视觉反馈并更新你的应用状态。

例如,下面是一段用于非常简单的滑块输入的代码:

// Step 1
const slider = interact('.slider')    // target elements with the "slider" class
​
slider
  // Step 2
  .draggable({                        // make the element fire drag events
    origin: 'self',                   // (0, 0) will be the element's top-left
    inertia: true,                    // start inertial movement if thrown
    modifiers: [
      interact.modifiers.restrict({
        restriction: 'self',           // keep the drag coords within the element
      }),
    ],
  })
  // Step 3
  .on('dragmove', function (event) {  // call this listener on every dragmove
    const sliderWidth = interact.getElementRect(event.target.parentNode).width
    const value = event.pageX / sliderWidth
​
    event.target.style.paddingLeft = (value * 100) + '%'
    event.target.setAttribute('data-value', value.toFixed(2))
  })
js

interact 函数接收一个元素或 CSS 选择器字符串,并返回一个 Interactable 对象。这个对象拥有多种方法,可用于配置动作和事件监听器。指针从按下 → 移动 → 抬起的交互序列会启动拖拽、缩放或手势动作。通过为这些动作添加事件监听函数,你可以响应 InteractEvent,从中获取指针坐标、速度、元素尺寸等信息。

动作

interact.js 支持 3 种基本动作类型,它们都由指针按下 → 移动 → 抬起的序列触发:

  • Draggable:用于移动元素,或在 canvas 上绘制内容。 它可以结合 dropzones 来实现拖放应用。
  • Resizable:用于在用户通过指针移动元素一条或两条边时,监听元素的尺寸和位置变化。
  • Gesturable:用于处理双指手势,并提供角度、缩放比例等数据。

Pro 基于 draggable 动作提供了 Sortable 和 Swappable 功能,用于实现列表元素的拖放重排。


安装

interact.js 提供了两组免费的包,你可以将它们添加到项目中:

  1. 如果你想快速开始,可以使用 npm 上名为 interactjs 的包。 这个包包含该库的所有功能,并以 ES5 打包版本 的形式提供。
  2. 如果你希望减小 JS 负载体积,可以使用 @interactjs/ 作用域下的 npm 包,它们允许你选择要包含哪些功能。 这些包以 ES6 模块 的形式发布,对于较旧的浏览器可能需要进行转译。

npm 预打包版本

# install pre-bundled package with all features
$ npm install --save interactjs
sh
// es6 import
import interact from 'interactjs'
js
// or if using commonjs or AMD
const interact = require('interactjs')
js

如果要通过 npm 使用预打包版本,请使用 npm install interactjs 将该包安装为依赖,然后在你的 JavaScript 文件中 import 或 require 这个包。

CDN 预打包版本

<script src="https://cdn.jsdelivr.net/npm/interactjs/dist/interact.min.js"></script>
<!-- or -->
<script src="https://unpkg.com/interactjs/dist/interact.min.js"></script>
html

你也可以使用 jsDelivrunpkg CDN,只需添加一个指向它们服务器的 <script> 标签即可。

根据运行环境所支持的模块格式,interact 会以 CommonJS 模块、AMD 模块或全局变量的形式暴露。

# install just the type definitions
$ npm install --save-dev @interactjs/types
sh

如果你只通过 CDN 使用这个库,但希望在开发时获得 TypeScript 类型定义,可以将 @interactjs/types 包安装为开发依赖。

npm 精简版本

# install only the features you need
$ npm install --save @interactjs/interact \
  @interactjs/auto-start \
  @interactjs/actions \
  @interactjs/modifiers \
  @interactjs/dev-tools
sh
import '@interactjs/auto-start'
import '@interactjs/actions/drag'
import '@interactjs/actions/resize'
import '@interactjs/modifiers'
import '@interactjs/dev-tools'
import interact from '@interactjs/interact'
​
interact('.item').draggable({
  listeners: {
    move (event) {
      console.log(event.pageX, event.pageY)
    },
  },
})
js

为了获得更精简的 JS 负载,你可以根据所需的每个功能分别安装并导入对应的包:

包名说明
@interactjs/interact(必需) 提供 interact() 方法
@interactjs/actions拖拽、缩放、手势动作
@interactjs/auto-start通过指针按下、移动序列启动动作
@interactjs/modifiers吸附、限制等修饰器
@interactjs/snappers提供 interact.snappers.grid() 工具
@interactjs/inertia拖拽和缩放的惯性式抛掷效果
@interactjs/reflowinteractable.reflow(action) 方法,用于触发修饰器和事件监听器
@interactjs/dev-tools针对常见错误的控制台警告(当 NODE_ENV === 'production' 时会被优化掉),以及用于生产构建优化的 babel 插件

CDN 精简版本

<script type="module">
  import 'https://cdn.interactjs.io/v1.9.20/auto-start/index.js'
  import 'https://cdn.interactjs.io/v1.9.20/actions/drag/index.js'
  import 'https://cdn.interactjs.io/v1.9.20/actions/resize/index.js'
  import 'https://cdn.interactjs.io/v1.9.20/modifiers/index.js'
  import 'https://cdn.interactjs.io/v1.9.20/dev-tools/index.js'
  import interact from 'https://cdn.interactjs.io/v1.9.20/interactjs/index.js'
​
  interact('.item').draggable({
    onmove(event) {
      console.log(event.pageX, event.pageY)
    },
  })
</script>
html

上面的这些包也可以通过 https://cdn.interactjs.io/v[VERSION]/[UNSCOPED_NAME] 获取。你可以在支持 ES6 import 的现代浏览器中导入它们。

Ruby on Rails

Rails 5.1+ 支持 yarn 包管理器,因此你可以通过运行 yarn add interactjs 将 interact.js 添加到你的应用中。然后按下面的方式 require 这个库:

//= require interactjs/interact
rb

可拖拽

拖拽是 interact.js 提供的最简单的动作。要让一个元素可拖拽,请先使用你想要的目标创建一个 interactable,然后调用 draggable 方法并传入所需的选项。

<div class="draggable">Draggable Element</div>
html
.draggable {
  touch-action: none;
  user-select: none;
}
css
const position = { x: 0, y: 0 }
​
interact('.draggable').draggable({
  listeners: {
    start (event) {
      console.log(event.type, event.target)
    },
    move (event) {
      position.x += event.dx
      position.y += event.dy
​
      event.target.style.transform = `translate(${position.x}px, ${position.y}px)`
    },
  },
})
js

除了常见的 InteractEvent 属性之外,dragmove 事件还具有:

拖拽事件属性说明
dragEnter该 Interactable 被拖入的 dropzone
dragLeave该 Interactable 被拖出的 dropzone

请记得使用 CSS touch-action: none,以防止用户使用触摸指针拖拽时浏览器执行平移;同时使用 user-select: none 禁用文本选择。{.notice .info}

lockAxisstartAxis

// lock the drag to the starting direction
interact(singleAxisTarget).draggable({
  startAxis: 'xy'
  lockAxis: 'start'
});
​
// only drag if the drag was started horizontally
interact(horizontalTarget).draggable({
  startAxis: 'x'
  lockAxis: 'x'
});
javascript

用于控制拖拽动作轴向的选项有两个:startAxislockAxis

startAxis 设置初始移动必须处于哪个方向,动作才会开始。使用 'x' 要求用户必须先水平拖拽,使用 'y' 则要求用户必须先垂直拖拽。

lockAxis 会让拖拽事件只在指定轴向上发生变化。如果使用 'start' 作为值,那么拖拽会被锁定到起始方向。


可调整尺寸

interact(target)
  .resizable({
    edges: {
      top   : true,       // Use pointer coords to check for resize.
      left  : false,      // Disable resizing from left edge.
      bottom: '.resize-s',// Resize if pointer target matches selector
      right : handleEl    // Resize if pointer target is the given Element
    }
  })
javascript

缩放事件具有 rectdeltaRect 属性。rect 会在每个 resizemove 事件中更新,而 deltaRect 中的值表示变化量。在 resizestart 中,rectinteractable.getRect(element) 返回的矩形相同,并且 deltaRect 的所有属性都是零。

缩放事件属性说明
edges正在被改变的元素边
rect包含目标新尺寸的对象
deltaRect自上一个事件以来尺寸发生的变化

Resizable 选项具有一个 edges 属性,它用于指定元素可以从哪些边进行缩放(top、left、bottom 或 right)。

<div data-x="0" data-y="0" class="resizable">
  <!-- top-left resize handle -->
  <div class="resize-top resize-left"></div>
​
  <!-- bottom-right resize handle -->
  <div class="resize-bottom resize-right"></div>
</div>
html
interact('.resizable').resizable({
  edges: { top: true, left: true, bottom: true, right: true },
  listeners: {
    move (event) {
      let { x, y } = event.target.dataset
​
      x = (parseFloat(x) || 0) + event.deltaRect.left
      y = (parseFloat(y) || 0) + event.deltaRect.top
​
      Object.assign(event.target.style, {
        width: `${event.rect.width}px`,
        height: `${event.rect.height}px`,
        transform: `translate(${x}px, ${y}px)`,
      })
​
      Object.assign(event.target.dataset, { x, y })
    },
  },
})
js

请记得使用 CSS touch-action: none,以防止用户使用触摸指针拖拽时浏览器执行平移;使用 user-select: none 禁用文本选择;如果你的元素带有会影响宽度的 padding 和 border,还要使用 box-sizing: border-box。{.notice .info}

如果你希望某个元素表现为缩放角点,可以让它同时匹配两个相邻边的选择器。

缩放手柄元素必须是可缩放元素的子元素。如果你需要让手柄位于目标元素外部,那么需要使用 Interaction#start

invert

interact(target).resizable({
  edges: { bottom: true, right: true },
  invert: 'reposition'
})
javascript

默认情况下,缩放动作不能让 event.rect 小于 0x0。可以使用 invert 选项指定当目标将被缩放到小于 0x0 的尺寸时应该发生什么。可选值包括:

  • 'none'(默认):将缩放矩形限制为最小 0x0
  • 'negate':允许矩形具有负的宽度/高度
  • 'reposition':通过交换上下边和/或交换左右边,保持宽度/高度为正值

宽高比

interact(target).resizable({
  modifiers: [
    interact.modifiers.aspectRatio({
      // make sure the width is always double the height
      ratio: 2,
      // also restrict the size by nesting another modifier
      modifiers: [
        interact.modifiers.restrictSize({ max: 'parent' }),
      ],
    }),
  ],
})
js

interact.js 自带一个 aspectRatio 修饰器,可用于强制缩放后的矩形保持某个宽高比。该修饰器有 3 个选项:

属性类型说明
rationumber 或 'preserve'要保持的宽高比,或者使用 'preserve' 来保持初始宽高比
equalDeltaboolean让各边增加相同的量,而不是保持相同比例
modifiers修饰器数组应用于缩放的修饰器,这些修饰器会遵守宽高比选项

为了保证宽高比选项能够被其他修饰器尊重,这些修饰器必须放在 aspectRatio.modifiers 数组选项中,而不是aspectRatio 放在同一个 resize.modifiers 数组中。


手势

<div id="rotate-area">
  <div id="angle-info">0&deg;</div>
  <svg id="arrow" viewbox="0 0 100 100">
    <polygon points="50,0 75,25 62.5,25 62.5,100 37.5,100 37.5,25 25,25" fill="#29e"></polygon>
  </svg>
</div>
html
.draggable {
  touch-action: none;
  user-select: none;
}
css
var angle = 0
​
interact('#rotate-area').gesturable({
  onmove: function (event) {
    var arrow = document.getElementById('arrow')
​
    angle += event.da
​
    arrow.style.webkitTransform =
    arrow.style.transform =
      'rotate(' + angle + 'deg)'
​
    document.getElementById('angle-info').textContent =
      angle.toFixed(2) + '\u00b0'
  },
})
js

当两个指针按下并移动时,会触发手势事件。在手势事件中,page 和 client 坐标是触摸坐标的平均值,速度也会根据这些平均值计算。事件还具有以下属性:

手势事件属性说明
distance事件前两个触点之间的距离
angle两个触点连线形成的角度
da自上一个事件以来角度的变化量
scale起始事件的距离与当前事件距离之间的比值
ds自上一个事件以来缩放比例的变化量
box包围所有触点的矩形框

请记得使用 CSS touch-action: none,以防止用户使用触摸指针拖拽时浏览器执行平移;同时使用 user-select: none 禁用文本选择。{.notice .info}


落入区

Dropzone 定义了可拖拽目标能够被“放入”的元素,以及哪些元素会被接受。与拖拽事件一样,drop 事件不会修改 DOM 来重新设置元素的父级。如果你需要这样做,必须在自己的事件监听器中完成。

interact(dropTarget)
  .dropzone({
    ondrop: function (event) {
      alert(event.relatedTarget.id
            + ' was dropped into '
            + event.target.id)
    }
  })
  .on('dropactivate', function (event) {
    event.target.classList.add('drop-activated')
  })
javascript

Dropzone 事件

Dropzone 事件是普通对象,具有以下属性:

属性说明
targetdropzone 元素
dropzonedropzone 对应的 Interactable
relatedTarget正在被拖拽的元素
draggable正在被拖拽的 Interactable
dragEvent相关的拖拽事件 —— drag{start,move,end}
timeStamp事件发生时间
type事件类型
interact('.dropzone').dropzone({
  accept: '.drag0, .drag1',
});
javascript

accept

Dropzone 的 accept 选项是一个 CSS 选择器或元素。被拖拽元素必须与它匹配,drop 事件才会被触发。

interact(target).dropzone({
  overlap: 0.25
});
javascript

overlap 选项用于设置如何判断是否发生 drop。允许的值包括:

  • 'pointer':指针必须位于 dropzone 上方(默认)
  • 'center':可拖拽元素的中心必须位于 dropzone 上方
  • 0 到 1 之间的数字,表示(相交面积)/(可拖拽元素面积)。例如 0.5 表示当可拖拽元素一半面积位于 dropzone 上方时发生 drop

checker

checker 选项是一个函数,你可以设置它来额外检查某个被拖拽元素是否可以被放入 dropzone。

interact(target).dropzone({
  checker: function (
    dragEvent,         // related dragmove or dragend
    event,             // Touch, Pointer or Mouse Event
    dropped,           // bool default checker result
    dropzone,          // dropzone Interactable
    dropzoneElement,   // dropzone element
    draggable,         // draggable Interactable
    draggableElement   // draggable element
  ) {
​
    // only allow drops into empty dropzone elements
    return dropped && !dropElement.hasChildNodes();
  }
});
javascript

checker 函数接收以下参数:

参数说明
dragEvent相关的 dragmove 或 dragend 事件
event与 dragEvent 相关的用户 move/up/end 事件
dropped默认 drop 检查器返回的值
dropzonedropzone interactable
dropElementdropzone 元素
draggable正在被拖拽的 Interactable
draggableElement实际正在被拖拽的元素

事件

InteractEvents

<div>Drag, resize, or perform a multi-touch gesture</div>
html
.target {
  display: inline-block;
  min-height: 3rem;
  background-color: #29e;
  color: white;
  padding: 1rem;
  border-radius: 0.75rem;
}
css
function listener(event) {
  event.target.textContent = `${event.type} at ${event.pageX}, ${event.pageY}`
}
​
interact(target)
  .on('dragstart', listener)
  .on('dragmove dragend', listener)
  .on(['resizemove', 'resizeend'], listener)
  .on({
    gesturestart: listener,
    gestureend: listener,
  })
​
interact(target).draggable({
  onstart: listener,
  onmove: listener,
  onend: listener,
})
​
interact(target).resizable({
  listeners: [
    {
      start: function (event) {
        console.log(event.type, event.pageX, event.pageY)
      },
    },
  ],
})
javascript

InteractEvent 会针对不同动作被触发。事件类型包括:

  • Draggable:dragstartdragmovedraginertiastartdragend
  • Resizable:resizestartresizemoveresizeinertiastartresizeend
  • Gesturable:gesturestartgesturemovegestureend

要响应 InteractEvent,你必须在已配置对应动作的 interactable 上为这些事件类型添加监听器。创建出来的事件对象会作为第一个且唯一的参数传递给这些函数。

InteractEvent 属性包含鼠标/触摸事件的常见属性,例如 pageX/YclientX/Y、修饰键等;同时还包含一些用于提供坐标变化和事件特定数据的信息。下表展示了这些事件属性。

通用

属性说明
target正在被交互的元素
interactable正在被交互的 Interactable
interaction该事件所属的 Interaction
x0, y0起始事件的页面 x 和 y 坐标
clientX0, clientY0起始事件的客户端 x 和 y 坐标
dx, dy鼠标/触摸坐标的变化量
velocityX, velocityY指针速度
speed指针速率
timeStamp事件对象创建时间

拖拽

属性说明
dragmove
dragEnter该 Interactable 被拖入的 dropzone
dragLeave该 Interactable 被拖出的 dropzone

缩放

属性说明
edges正在被改变的元素边
rect包含目标新尺寸的对象
deltaRect自上一个事件以来尺寸发生的变化

手势

属性说明
distance事件前两个触点之间的距离
angle两个触点连线形成的角度
da自上一个事件以来角度的变化量
scale起始事件的距离与当前事件距离之间的比值
ds自上一个事件以来缩放比例的变化量
box包围所有触点的矩形框

在手势事件中,page 和 client 坐标是触摸坐标的平均值,速度也会根据这些平均值计算。

Drop 事件

interact(dropTarget)
  .dropzone({
    ondrop: function (event) {
      alert(event.relatedTarget.id + ' was dropped into ' + event.target.id)
    },
  })
  .on('dropactivate', function (event) {
    event.target.classList.add('drop-activated')
  })
javascript

Dropzone 可以接收以下事件:dropactivatedropdeactivatedragenterdragleavedropmovedrop

Dropzone 事件是普通对象,具有以下属性:

属性说明
targetdropzone 元素
dropzonedropzone 对应的 Interactable
relatedTarget正在被拖拽的元素
draggable正在被拖拽的 Interactable
dragEvent相关的拖拽事件 —— drag{start,move,end}
timeStamp事件发生时间
type事件类型

指针事件

interact(target).on('hold', function (event) {
  console.log(event.type, event.target)
})
javascript
  • down
  • move
  • up
  • cancel
  • tap
  • doubletap
  • hold

我称这些为 pointerEvents(小写 “p”),因为它们大致按照真实的 PointerEvent 接口来呈现事件,具体来说:

  • event.pointerId 提供 TouchEvent#identifierPointerEvent#pointerId,或者对于 MouseEvent 提供 undefined
  • event.pointerType 提供指针类型
  • 触摸事件之后不会产生模拟鼠标事件

事件属性可能会因浏览器和设备支持的事件接口不同而有所变化。例如,来自 touchstartdown 事件不会像 PointerEvent 接口规定的那样提供倾斜或压力信息。{.notice .info}

配置指针事件

interact(target).pointerEvents({
  holdDuration: 1000,
  ignoreFrom: '[no-pointer]',
  allowFrom: '.handle',
  origin: 'self',
})
javascript

pointerEvent 不会被吸附或限制,但可以通过 origin 修改进行调整。tap 事件具有一个 dt 属性,表示相关 downup 事件之间的时间。对于 doubletapdt 是前两次点击之间的时间。对于 hold 事件,dt 是指针保持按下的时长(约 600ms)。

快速点击

// fast click
interact('a[href]').on('tap', function (event) {
  window.location.href = event.currentTarget.href
  event.preventDefault()
})
javascript

tapdoubletap 没有移动设备上 click 事件存在的延迟,因此非常适合用于快速按钮和锚点链接。另外,与普通 click 事件不同,如果指针在释放前发生了移动,就不会触发 tap。


修饰器

// create a restrict modifier to prevent dragging an element out of its parent
const restrictToParent = interact.modifiers.restrict({
  restriction: 'parent',
  elementRect: { left: 0, right: 0, top: 1, bottom: 1 },
})
​
// create a snap modifier which changes the event coordinates to the closest
// corner of a grid
const snap100x100 = interact.modifiers.snap({
  targets: [interact.snappers.grid({ x: 100, y: 100 })],
  relativePoints: [{ x: 0.5, y: 0.5 }],
})
​
interact(target)
  .draggable({
    // apply the restrict and then the snap modifiers to drag events
    modifiers: [restrictToParent, snap100x100],
  })
  .on('dragmove', event => console.log(event.pageX, event.pageY))
js

interactmodifiers 允许你改变动作事件的坐标。传递给动作方法的 options 对象可以包含一个 modifiers 数组,这个数组会应用到该动作类型的事件上。数组中的修饰器会按顺序依次应用,它们的顺序可能会影响最终结果。

const snapAtEnd = interact.modifiers.snap({
  endOnly: true,
  targets: [/* ... */],
})
js

通过将修饰器的 endOnly 选项设置为 true,可以让修饰器只应用于一次交互中的最后一个 move 事件。当 endOnly 修饰器与启用了 inertia 的动作一起使用时,事件坐标会从抬起时的坐标平滑移动到修饰后的坐标。

interact.js 自带几种不同类型的修饰器,用于吸附限制元素。


限制

interact.js 通过 interact.modifiers 对象提供了 3 种限制修饰器:

  • 基于指针坐标的 restrict
  • 基于元素矩形的限制 restrictRect
  • 基于元素尺寸的 restrictSize(仅用于缩放)
  • 以及基于元素边缘的 restrictEdges(仅用于缩放)

restrict()

interact(target).draggable({
  modifiers: [
    interact.modifiers.restrict({
      restriction: 'parent',
      endOnly: true
    })
  ]
})
javascript

restriction 值用于指定动作被限制在什么区域内。该值可以是:

  • 一个 rect 对象,包含 topleftbottomright,或者包含 xywidthheight
  • 一个元素,其尺寸会被用作限制区域
  • 一个函数,接收 (x, y, element) 并返回一个 rect 或元素
  • 以下字符串之一:
  • 'self':限制在目标元素的矩形范围内
  • 'parent':限制在元素父节点的矩形范围内,或者
  • 一个 CSS 选择器字符串:如果目标元素的某个父元素匹配该选择器,则会使用它的矩形作为限制区域。

restrictRect()

使用 restrict 变体时,限制默认是相对于指针坐标进行的,因此会保证动作坐标,而不是元素尺寸,被保持在限制区域内。你可以使用 restrictRect 变体,让拖拽时也考虑元素的边缘。

interact(target).draggable({
  modifiers: [
    interact.modifiers.restrictRect({
      restriction: 'parent'
    })
  ]
})
javascript

如果目标元素大于限制区域,那么该元素仍然允许在限制区域周围移动。

elementRect

restrictRectrestrict 相同,但 elementRect 选项被设置为一个有用的默认值:{ left: 0, right: 0, top: 1, bottom: 1 }elementRect 选项用于指定元素中要被视为边缘的区域,其值是从左上边到右下边的标量。

对于 leftright 属性,0 表示元素左边缘,1 表示元素右边缘。对于 topbottom0 表示元素上边缘,1 表示元素下边缘。

{ top: 0.25, left: 0.25, bottom: 0.75, right: 0.75 } 表示允许元素的四分之一超出限制边界。

restrictSize()

interact(target).resizable({
  modifiers: [
    interact.modifiers.restrictSize({
      min: { width: 100, height: 100 },
      max: { width: 500, height: 500 }
    })
  ]
})
javascript

restrictSize 允许你指定目标元素在缩放时必须满足的最小和最大尺寸。

restrictEdges()

interact(target).resizable({
  modifiers: [
    interact.modifiers.restrictEdges({
      inner: {
        left: 100,  // the left edge must be <= 100
        right: 200  // the right edge must be >= 200
      }
      outer: {
        left: 0,    // the left edge must be >= 0
        right: 300  // the right edge must be <= 300
      }
    })
  ]
})
javascript

restrictEdges 允许你指定目标元素在缩放时必须满足的 innerouter 尺寸。你可以把 inner 理解为设置元素的最小尺寸,把 outer 理解为最大尺寸。


工具与优化

功能选择

# install only the features you need
$ npm install --save @interactjs/interact \
  @interactjs/auto-start \
  @interactjs/actions \
  @interactjs/modifiers \
  @interactjs/dev-tools
sh
import '@interactjs/auto-start'
import '@interactjs/actions/drag'
import '@interactjs/actions/resize'
import '@interactjs/modifiers'
import '@interactjs/dev-tools'
import interact from '@interactjs/interact'
​
interact('.item').draggable({
  listeners: {
    move (event) {
      console.log(event.pageX, event.pageY)
    },
  },
})
js

将非作用域的 interactjs npm 包添加到项目中,是开始使用这个库最简单的方式,因为它已经包含所有功能,并且预打包、编译为 ES5 语法。不过,这可能会导致许多未使用的功能也被包含进来,从而增加 JS 负载体积。

如果想要更精简的构建,可以按需导入每个功能对应的包。更多细节以及可用包列表,请参阅 npm 精简安装文档

@interactjs/dev-tools

@interactjs/dev-tools 包会提供一些提示,帮助你在开发应用时避免常见问题(例如缺少事件处理器、缺少有用的 CSS 样式等)。尽管这些提示很有帮助,但最好避免将它们包含在生产部署中。下面提供了一些实现方式。

面向生产环境优化

Babel 插件

// babel config
{
  "env": {
    "production": {
      "plugins": [
        "@interactjs/dev-tools/babel-plugin-prod",
      ]
    }
  }
}
json
// source file
import '@interactjs/actions/drag'
import interact from '@interactjs/interact'
js
// result
import '@interactjs/actions/drag/index.prod'
import interact from '@interactjs/interact/index.prod'
js

如果你在部署流程中使用 babel,可以直接将 @interactjs/dev-tools/babel-plugin-prod 添加到生产环境 babel 配置的 plugins 部分。这样,所有 @interactjs/** 导入都会被改写为经过优化的生产版本,并移除开发提示。

不使用构建工具

import '@interactjs/actions/drag/index.prod'
import interact from '@interactjs/interact/index.prod'
js

如果你不使用 babel,那么需要修改 import,使其包含 .prod 扩展名。对于目录的 index 文件,还需要添加文件名(例如 @interactjs/actions -> @interactjs/actions/index.prod)。


动作选项

Interactable 方法 draggable()resizable()gesturable() 用于为目标元素启用和配置动作。它们都有一些通用选项,也有一些动作专属选项和事件属性。

拖拽、缩放和手势交互都会触发 InteractEvent,这些事件具有以下所有动作类型共有的属性:

InteractEvent 属性说明
target正在被交互的元素
interactable正在被交互的 Interactable
interaction该事件所属的 Interaction
x0, y0起始事件的页面 x 和 y 坐标
clientX0, clientY0起始事件的客户端 x 和 y 坐标
dx, dy鼠标/触摸坐标的变化量
velocityX, velocityY指针速度
speed指针速率
timeStamp事件对象创建时间

通用动作选项

Interactable 方法 draggableresizablegesturable 可以接收 truefalse,用于简单地允许/禁止该动作;也可以接收一个对象,通过对象属性修改某些设置。

max

max 用于限制可以同时以一个 interactable 为目标的并发交互数量。默认情况下,任意数量的交互都可以以一个 interactable 为目标。

maxPerElement

默认情况下,同一个 interactable+element 组合只能被 1 个交互作为目标。如果你想允许同一个目标元素上存在多个交互,请将对象的 maxPerElement 属性设置为 >= 2 的值。

manualStart

如果将其改为 true,那么拖拽、缩放和手势动作都必须通过调用 Interaction#start 来启动;通常的 downmove<action>start... 序列不会再启动动作。参见 auto-start

hold

指针按下并保持指定毫秒数之后,动作才会开始。

inertia

修改拖拽和缩放的惯性设置。参见 docs/inertia

styleCursor

如果启用了 auto-start 功能,interact 会在你悬停到可拖拽和可缩放元素上时设置光标样式。

interact(target).styleCursor(false)
js

要对所有动作禁用该行为,请将 styleCursor 选项设置为 false

cursorChecker

interact(target)
  .resizable({
    edges: { left: true, right: true },
    cursorChecker (action, interactable, element, interacting) {
      // the library uses biderectional arrows <-> by default,
      // but we want specific arrows (<- or ->) for each diriection
      if (action.edges.left) { return 'w-resize' }
      if (action.edges.right) { return 'e-resize' }
    },
  })
  .draggable({
    cursorChecker () {
      // don't set a cursor for drag actions
      return null
    },
  })
js

你可以通过 interact(target).styleCursor(false) 禁用默认光标,但这会禁用所有动作的光标样式。要按动作分别禁用或修改光标,可以设置一个 cursorChecker 函数。该函数接收当前交互的信息,并返回要设置到目标元素上的 CSS cursor 值。

autoScroll

interact(element)
  .draggable({
    autoScroll: true,
  })
  .resizable({
    autoScroll: {
      container: document.body,
      margin: 50,
      distance: 5,
      interval: 10,
      speed: 300,
    }
  })
javascript

当拖拽或缩放移动发生在容器边缘时,滚动某个容器(window 或 HTMLElement)。

allowFrom(手柄)

<div class="movable-box">
  <div class="drag-handle" />
  Content
  <div class="resize-handle" />
</div>
html
interact('.movable-box')
  .draggable({
    allowFrom: '.drag-handle',
  })
  .resizable({
    allowFrom: '.resize-handle',
  })
  .pointerEvents({
    allowFrom: '*',
  })
javascript

allowFrom 选项允许你指定一个目标 CSS 选择器或 Element。指针按下事件的目标必须匹配它,动作才会开始。该选项可用于拖拽、缩放和手势,也可用于 pointerEvents(down、move、hold 等)。使用 allowFrom 选项时,你可以为每个动作分别指定手柄,也可以为所有 pointerEvents 监听器指定手柄。

allowFrom 元素必须是目标 interactable 元素的子元素。{.notice .info}

ignoreFrom

<div id="movable-box">
  <p class="content">Selectable text</p>
  <div no-pointer-event>Should not fire tap, hold, etc. events</div>
</div>
html
var movable = document.querySelector('#movable-box')
​
interact(movable)
  .draggable({
    ignoreFrom: '.content',
    onmove: function (event) {
      /* ... */
    }
  })
  .pointerEvents({
    ignoreFrom: '[no-pointer-event]',
  })
  .on('tap', function (event) {
  })
javascript

作为 allowFrom 的补充,ignoreFrom 允许你指定目标内部的一些元素,使其不会触发动作启动。这在某些元素需要保留默认交互行为时很有用。例如,对于文本或 contentEditable 区域,你可以将它们包裹在一个可拖拽元素中,并忽略可编辑内容,这样就可以在不移动元素的情况下继续高亮文本。

enabled

为 Interactable 启用动作。如果 options 对象没有 enabled 属性,或者该属性值为 true,则动作会被启用。如果 enabled 为 false,则动作会被禁用。


Reflow

reflow 方法允许你触发一个 action start -> move -> end 序列,该序列会运行修饰器并执行 drop 计算等。如果你的 interactable 目标是 CSS 选择器,那么每个匹配元素都会运行一次交互。

如果元素启用了惯性、endOnly 修饰器和 smoothEndDuration,那么交互可能不会立即结束。reflow 方法返回一个 Promise,会在所有交互完成后 resolve。因此,你可以对多个 reflow 使用 await.then()

const interactable = interact(target).draggable({}).resizable({})
​
async function onWindowResize () {
  // start a resize action and wait for inertia to finish
  await interactable.reflow({ name: drag, axis: 'x' })
​
  // start a drag action
  await interactable.reflow({
    name: 'resize',
    edges: { left: true, bottom: true },
  })
}
​
window.addEventListener(onWindowResize, 'resize')
js

惯性

interact(target)
  .draggable({
    inertia: true
  })
  .resizable({
    inertia: {
      resistance: 30,
      minSpeed: 200,
      endSpeed: 100
    }
  })
javascript

惯性允许拖拽和缩放动作在用户以足够快的速度释放指针之后继续运动。所需的启动速度、结束速度和阻力可以通过下面的设置进行可选配置。

如果动作结束时没有惯性,但通过 endOnly 选项进行了吸附或限制,那么坐标会从结束坐标插值过渡到吸附/限制后的坐标。

选项

  • resistance 是一个大于零的数字,用于设置动作减速的速率。值越高,减速越快。
  • endSpeed 是动作被认为已经停止时的速度(像素/秒)。
  • allowResume 是一个 boolean 值,表示是否允许用户在惯性阶段继续恢复某个动作。
  • smoothEndDuration 是从实际结束坐标到使用 endOnly 后修饰坐标之间的插值移动时长(毫秒)。将该值设置为 0 可以禁用带有 endOnly 吸附/限制时的结束过渡。

当惯性被恢复时,相对于目标左上角的起始坐标和恢复坐标之间的差值,不会反映在接下来的 {action}move 事件中。相反,当指针在惯性期间重新按下时,会先触发一个 {action}resume 事件,然后才会再次触发常规的 “{action}move” 事件。如果你需要坐标差值,应该监听这个事件,并像处理 {action}move 事件一样响应它。


吸附

interact.js 通过 interact.modifiers 对象提供了 3 种 snap 修饰器:

  • 基于指针坐标的 snap,最适合拖拽动作;
  • snapSize 只适用于缩放动作,允许你为目标元素尺寸设置吸附目标;
  • snapEdgessnapSize 类似,但允许你设置目标元素各条边的目标位置。

创建 snap 修饰器时,选项中会包含一个 targets 数组。动作事件会被吸附到这个数组中处于范围内且距离最近的目标。

snap()

snap 修饰器会在指针坐标进入指定目标的范围时,将指针坐标改为这些目标坐标。

const mySnap = interact.modifiers.snap({
  targets: [
    { x: 200, y: 200 },
    { x: 250, y: 350 },
  ],
})
js

在拖拽时使用 snap 修饰器,拖拽事件监听器接收到的指针坐标会被修改为符合吸附目标坐标的值。该选项也可以用于可缩放目标,但可能不会得到直观的结果。

snap 目标具有 xy 数字属性,以及一个可选的 range 数字属性。

relativePoints

interact(element).draggable({
  modifiers: [
    interact.modifiers.snap({
      targets: [ { x: 300, y: 300 } ],
      relativePoints: [
        { x: 0  , y: 0   },   // snap relative to the element's top-left,
        { x: 0.5, y: 0.5 },   // to the center
        { x: 1  , y: 1   }    // and to the bottom-right
      ]
    })
  ]
})
javascript

如果你想为 snap(不是 snapSizesnapEdges)指定元素上的哪些点用于相对吸附,可以使用 relativePoints 数组。数组中的每一项都应该是一个包含 xy 属性的对象,这些属性是标量,用于指定元素上吸附应相对于的位置。如果没有指定 relativePoints 数组,或者数组为空,那么吸附会相对于指针坐标(默认)。

在执行吸附计算时,实际上会有 targets.length * max( relativePoints.length, 1 ) 个吸附目标。Snap 函数会在每个 relativePoint 的坐标下被调用多次。

offset

interact(element1).draggable({
  modifiers: [
    interact.modifiers.snap({
      targets: [ { x: 300, y: 300 } ],
      offset: { x: 20, y: 20 }
    })
  ]
})
​
interact(element2).resizable({
  modifiers: [
    interact.modifiers.snap({
      targets: [ { x: 300, y: 300 } ],
      offset: 'startCoords'
    })
  ]
})
javascript

offset 选项允许你偏移 snap 修饰器目标的坐标。该值可以是:

  • 一个包含 xy 属性的对象;
  • 'startCoords',表示使用动作开始时的 pageXpageY
  • 'self',表示使用目标元素的左上角坐标;
  • 'parent',表示使用目标父元素的左上角坐标。

snapSize()

interact(target).resizable({
  edges: { top: true, left: true },
  modifiers: [
    interact.modifiers.snapSize({
      targets: [
        { width: 100 },
        interact.snappers.grid({ width: 100, height: 100 }),
      ],
    }),
  ],
})
js

snapSize 修饰器会在缩放时吸附目标的尺寸snapSize 目标是一个对象,包含 xy 数字属性, widthheight 数字属性,并且可以带有可选的 range。它的 targets 具有 xy 数字属性, widthheight 数字属性,以及一个可选的 range

snapEdges()

interact(target).resizable({
  edges: { top: true, left: true },
  modifiers: [
    interact.modifiers.snapEdges({
      targets: [
        interact.snappers.grid({ top: 100, left: 100 }),
      ],
    }),
  ],
})
js

snapEdges 修饰器会在缩放时吸附目标的边缘。它的 targets 可以包含 xy 数字属性,用于分别吸附 left/right 和 top/bottom 边;也可以包含 topleftwidthheight 数字属性,用于分别吸附每条边,并且可以带有可选的 range

targets 选项

动作事件的坐标会与所提供 snap 修饰器的 targets 进行比较。如果有多个目标处于范围内,则使用距离最近的目标。

interact.modifiers.snap({
  targets: [
    function (
      // the x and y page coordinates,
      x,
      y,
      // the current interaction
      interaction,
      // the offset information with relativePoint if set
      { x: offsetX, y: offsetY, relativePoint, index: relativePointIndex },
      // the index of this function in the options.targets array
      index,
    ) {
      return {
        x: x,
        y: 75 + 50 * Math.sin(x * 0.04),
        range: 40,
      }
    },
  ],
})
js

你可以在 targets 数组中使用函数。如果某个 snap 目标是函数,那么它会被调用,并接收事件的 xy 坐标作为前两个参数,interaction 作为第三个参数。函数的返回值会被用作目标。

如果目标省略了某个轴或边属性,则对应的轴不会被改变。例如,如果目标被定义为 { y: 100, range Infinity },那么吸附后的移动会在 (100, pointerEventPageX) 处保持水平。

吸附网格

var gridTarget = interact.snappers.grid({
  // can be a pair of x and y, left and top,
  // right and bottom, or width, and height
  x: 50,
  y: 50,
​
  // optional
  range: 10,
​
  // optional
  offset: { x: 5, y: 10 },
​
  // optional
  limits: {
    top: 0,
    left: 0,
    bottom: 500,
    height: 500
  }
})
​
interact(element).draggable({
  modifiers: [
    interact.modifiers.snap({ targets: [gridTarget] })
  ]
})
javascript

你可以使用 interact.snappers.grid() 方法创建一个吸附到网格的目标。该方法接收一个描述网格的对象,并返回一个会吸附到该网格角点的函数。

网格的属性包括:

  • xy:水平和垂直网格线之间的间距。
  • range(可选):距离网格角点多远以内,指针坐标会被吸附。
  • offset(可选):包含 xy 属性的对象,用于偏移网格线。
  • limits(可选):包含 topleftbottomright 属性的对象,用于设置网格边界。

range

interact(element).draggable({
  modifiers: [
    interact.modifiers.snap({
      targets: [
        { x: 20, y: 450, range: 50 }
        { x: 10, y: 0 /* use default range below */ }
      ],
      range: 300 // for targets that don't specify a range
    })
  ]
})
javascript

可以在 snap 修饰器选项中指定 range,每个目标也可以选择拥有自己的 range。snap 目标的 range 是指针必须距离目标坐标多近,吸附才有可能发生。

也就是说:inRange = distance <= range

事件吸附信息

interact(target).draggable({
  modifiers: [
    interact.modifiers.snap({ targets: [(x, y) => ({ x: x + 20 })] }),
  ],
  listeners: {
    move (event) {
      console.log(event.modifiers[0].target.source)
    },
  },
})
js

InteractEvent.modifiers 会是一个数组,包含已为该动作设置的修饰器信息。Snap 修饰器会提供一个对象,其中包含最近目标以及计算出的偏移量。

属性类型说明
xynumber应用 origin、offset 和 relativePoint 后被吸附到的坐标
source目标对象或函数targets 数组选项中的目标对象或函数
indexnumbersource 在 targets 数组中的索引
rangenumber目标的范围
offsetobject应用于 source 的偏移

自动开始

预打包版本包含 auto-start 插件。当指针在已启用的目标元素上按下并移动时,该插件会启动交互。你可以通过将 manualStart 选项设置为 true 来为某个动作禁用此行为。

interact(target)
  .draggable({
    manualStart: true,
  })
  .on('doubletap', function (event) {
    var interaction = event.interaction
​
    if (!interaction.interacting()) {
      interaction.start(
        { name: 'drag' },
        event.interactable,
        event.currentTarget,
      )
    }
  })
js

使用 manualStart: true 时,你需要在指针事件监听器中调用 event.interaction.start(actionInfo) 来启动动作。由于库不再决定何时启动动作,因此光标不会被自动设置。


从 v1.2 迁移

最新版本修复了多个 bug,允许按动作分别设置更多选项,为 pointerEvents 添加了配置选项,并添加了多个新方法和选项。changelog 列出了所有主要变更。

每个动作独立的 modifiers 数组

现在,修饰器通过 interact.modifiers[modifierName](options) 方法创建。这些方法的返回值会放入 actionOptions.modifiers 数组中。这可以让你更轻松地复用修饰器配置,并指定它们的执行顺序。

// create a restrict modifier to prevent dragging an element out of its parent
const restrictToParent = interact.modifiers.restrict({
  restriction: 'parent',
  elementRect: { left: 0, right: 0, top: 1, bottom: 1 },
})
​
// create a snap modifier which changes the event coordinates to the closest
// corner of a grid
const snap100x100 = interact.modifiers.snap({
  targets: [interact.snappers.grid({ x: 100, y: 100 })],
  relativePoints: [{ x: 0.5, y: 0.5 }],
})
​
interact(target)
  .draggable({
    // apply the restrict and then the snap modifiers to drag events
    modifiers: [restrictToParent, snap100x100],
  })
  .on('dragmove', event => console.log(event.pageX, event.pageY))
js

改进的 resize snap 和 restrict

现在有几个新的 snap 和 restrict 修饰器可用于 resize 动作:

限制

  • 基于指针坐标的 restrict
  • 基于元素矩形的限制 restrictRect
  • 基于元素尺寸的 restrictSize(仅用于缩放)
  • 以及基于元素边缘的 restrictEdges(仅用于缩放)

吸附

  • 基于指针坐标的 snap,最适合拖拽动作;
  • snapSize 只适用于缩放动作,允许你为目标元素尺寸设置吸附目标;
  • snapEdgessnapSize 类似,但允许你设置目标元素各条边的目标位置。
interact(target).resize({
  edges: { bottom: true, right: true },
​
  // sizes at fixed grid points
  snapSize: {
    targets: [
      interact.snappers.grid({ x: 25, y: 25, range: Infinity }),
    ],
  },
​
  // minimum size
  restrictSize: {
    min: { width: 100, height: 50 },
  },
​
  // keep the edges inside the parent
  restrictEdges: {
    outer: 'parent',
    endOnly: true,
  },
})
js

Resize aspectRatio 修饰器

resize 的 preserveAspectRatiosquare 选项已经被 aspectRatio 修饰器取代,该修饰器可以与其他修饰器协同工作。

interact(target).resizable({
  edges: { left: true, bottom: true },
  modifiers: [
    interact.modifiers.aspectRatio({
      // ratio may be the string 'preserve' to maintain the starting aspect ratio,
      // or any number to force a width/height ratio
      ratio: 'preserve',
      // To add other modifiers that respect the aspect ratio,
      // put them in the aspectRatio.modifiers array
      modifiers: [interact.modifiers.restrictSize({ max: 'parent' })],
    }),
  ],
})
js
interact(target).resizable({
  modifiers: [
    interact.modifiers.aspectRatio({
      // The equalDelta option replaces the old resize.square option
      equalDelta: true,
    }),
  ],
})
js

已移除的方法

下表中的方法已被移除,并被新的 modifiers 数组 API 中的动作方法选项和修饰器方法取代:

方法替代方式
interactable.squareResize(bool)interact.modifiers.aspectRatio({ equalDelta: true })
interactable.snap({ actions: ['drag'], ...snapOptions })interact.modifiers.snap(snapOptions)
interactable.restrict(restrictOptions)interact.modifiers.restrict(restrictOptions)
interactable.inertia(true)interactable.draggable({ inertia: true })
interactable.accept('.can-be-dropped')interactable.dropzone({ accept: '.can-be-dropped' })
interact.margin(50)interactable.resizable({ margin: 50 })

动作结束事件的 dx/dy

dragendresizeendgestureend 事件中的 dxdy 字段以前表示起始坐标与结束坐标之间的差值。现在它们始终为 0(结束事件与最后一个 move 事件之间的差值)。请使用 event.X0event.Y0(或 event.clientX0event.clientY0)获取起始坐标,并用结束事件坐标减去它们。

interact(target).draggable({
  onend: function (event) {
    console.log(event.pageX - event.X0, event.pageY - event.Y0)
  },
})
js

Drop 事件

现在,dragend 事件会在 drop 事件之前触发。如果将会发生 drop 事件,可以使用 dragendEvent.relatedTarget 获取 dropzone 元素。

鼠标按钮

默认情况下,只有鼠标左键可以启动动作。可以使用 mouseButtons 动作选项来修改这一行为。


常见问题

本页面包含一些经常在 Gitter chatGithub issues 中被提出的问题和情况。

按住后开始动作

使用 hold 选项,它接收一个毫秒数,表示指针必须按住多久。

interact(target)
  .draggable({
    // start dragging after the pointer is held down for 1 second
    hold: 1000
  })
javascript

如果你遇到默认浏览器行为方面的问题,例如滚动、上下文菜单等,可以查看 Interactable#preventDefault 方法,以及这个 Github 线程

克隆目标并拖拽

<div class="item"></div>
html
interact('.item')
  .draggable({ manualStart: true })
  .on('move', function (event) {
    var interaction = event.interaction
​
    // if the pointer was moved while being held down
    // and an interaction hasn't started yet
    if (interaction.pointerIsDown && !interaction.interacting()) {
      var original = event.currentTarget,
        // create a clone of the currentTarget element
        clone = event.currentTarget.cloneNode(true)
​
      // insert the clone to the page
      // TODO: position the clone appropriately
      document.body.appendChild(clone)
​
      // start a drag interaction targeting the clone
      interaction.start({ name: 'drag' }, event.interactable, clone)
    }
  })
javascript

没有直接的 API 可以拖拽目标元素的克隆。不过,你可以使用 Interaction#start 将某次交互的目标改为你创建的任意元素。

移除 / 销毁 / 释放

interact(target).draggable(true).resizable(true)

interact.isSet(target) // true

interact(target).unset()

interact.isSet(target) // false
interact(target).draggable() // false
interact(target).resizable() // false
javascript

要移除一个 Interactable,请使用 interact(target).unset()。这应该会移除所有事件监听器,并让 interact.js 完全忘记该目标。

拖拽过程中改变 dropzone

interact.dynamicDrop(true)
javascript

如果你在拖拽过程中添加或移除 dropzone 元素,或者改变它们的尺寸,可能需要将 dynamicDrop 设置为 true,以便在每次 dragmove 之后重新计算 dropzone 的矩形。

拖拽手柄

<div class="item">
  A draggable item
  <div class="handle">Handle</div>
</div>
html
interact('.item').draggable({
  allowFrom: '.handle',
})
javascript

要让某个元素成为其父级可拖拽元素的手柄,请使用 allowFrom 设置选项,使动作只有在元素匹配某个 CSS 选择器或是某个特定元素时才会开始。

阻止子元素触发动作

<div class="resizable">
  A resizable item
  <textarea></textarea>
</div>
html
interact('.item')
  .draggable({
    // don't drag from textarea elments
    ignoreFrom: 'textarea',
  });
javascript

使用 ignoreFrom 选项,可以在指针按下位置匹配给定选择器或 HTMLElement 时,阻止动作开始。

还原 / 恢复 / 撤销拖拽位置

没有直接的 API 可以将被拖拽元素恢复到拖拽前的位置。要实现这一点,你必须在 dragstart 时保存位置,并在 dragend 时修改元素样式,让它回到起始位置。你可以使用 CSS transition 来为位置变化添加动画。

拖拽时变成滚动

.draggable, .resizable, .gesturable {
  -ms-touch-action: none;
  touch-action: none;
  user-select: none;
}
css

为了允许触摸交互且不触发滚动或缩放,请使用 touch-action CSS 属性

在 iFrame 之间拖拽

interact.js 对跨 iFrame 使用提供了有限支持。目前仍存在浏览器差异以及其他尚未解决的问题。