interact.js 用于拖放、调整大小和多点触控手势

interact.js 是一个 JavaScript 模块,用于拖放、调整大小和多点触控手势,并具有现代浏览器(以及 IE8+)的惯性和捕捉功能。

背景

我最初将其作为我的 GSoC 2012 项目 的一部分,用于 Biographer 的网络可视化工具。该工具是一个 Web 应用程序,它渲染到 SVG 画布并使用 jQuery UI 进行拖放、选择和调整大小。由于 jQuery UI 对 SVG 的支持有限,因此必须使用大量解决方法。我需要使 Web 应用程序在智能手机和平板电脑上更易用,而这项工作中最大的一块是将 jQuery UI 替换为 interact.js,它

  • 轻量级,
  • 与 SVG 兼容良好,
  • 处理多点触控输入,
  • 将渲染/样式化元素的任务留给应用程序,以及
  • 允许应用程序提供对象尺寸,而不是解析元素样式或获取 DOMRects。

interact.js 试图做的是在不同的浏览器和设备之间一致地呈现输入数据,并提供方便的方法来假装用户做了他们实际上没有做的事情(捕捉、惯性等)。

某些用户输入序列会导致 InteractEvent 被触发。如果您为事件类型添加事件侦听器,则该函数将获得一个 InteractEvent 对象,该对象提供指针坐标和速度,以及在手势事件中提供缩放比例、距离、角度等。interact.js 修改 DOM 的唯一时间是为光标设置样式;在拖动过程中使元素移动必须通过您自己的事件侦听器完成。这样,您就可以控制发生的一切。

滑块演示

这是一个使用 interact.js 创建滑块的示例。您可以在 CodePen 上查看和编辑此帖子中所有演示的完整 HTML、CSS 和 JS 代码。

查看 CodePen 上 Taye A (@taye) 的 interact.js 简单滑块

JavaScript 概述

interact('.slider')                   // target the matches of that selector
  .origin('self')                     // (0, 0) will be the element's top-left
  .restrict({drag: 'self'})           // keep the drag within the element
  .inertia(true)                      // start inertial movement if thrown
  .draggable({                        // make the element fire drag events
    max: Infinity                     // allow drags on multiple elements
  })
  .on('dragmove', function (event) {  // call this function on every move
    var sliderWidth = interact.getElementRect(event.target.parentNode).width,
        value = event.pageX / sliderWidth;

    event.target.style.paddingLeft = (value * 100) + '%';
    event.target.setAttribute('data-value', value.toFixed(2));
  });

interact.maxInteractions(Infinity);   // Allow multiple interactions
  • interact('.slider') [文档] 创建一个 Interactable 对象,该对象针对与 '.slider' CSS 选择器匹配的元素。HTML 或 SVG 元素对象也可以用作目标,但使用选择器可以让您对多个元素使用相同的设置。
  • .origin('self') [文档] 告诉 interact.js 修改报告的坐标,以便目标元素左上角的事件为 (0,0)
  • .restrict({drag: 'self'}) [文档] 使坐标保持在目标元素区域内。
  • .inertia(true) [文档] 允许用户“抛出”目标,以便指针释放后目标继续移动。
  • 在对象上调用 .draggable({max: Infinity}) [文档]
    • 允许在用户从与目标匹配的元素拖动时调用拖动侦听器,以及
    • 允许同时拖动多个目标元素
  • .on('dragmove', function (event) {...}) [文档] 添加了 dragmove 事件的侦听器。每当发生 dragmove 事件时,将调用添加到目标 Interactable 的该事件类型的所有侦听器。此处的侦听器函数根据滑块宽度上的哪个点发生了拖动来计算 0 到 1 之间的值。此值用于定位手柄。
  • interact.maxInteractions(Infinity) [文档] 是启用任何目标上的多个交互所需的。默认值为 1,以保持向后兼容性。

interact.js 解决了许多浏览器实现差异。MouseEvents、TouchEvents 和 PointerEvents 将生成相同的拖动事件对象,因此此滑块在 iOS、Android、Firefox OS 和 Windows RT 以及桌面浏览器(包括 IE8 及更高版本)上都能正常工作。

彩虹像素画布演示

interact.js 不仅仅用于在页面上移动元素。在这里,我使用它在画布元素上绘制。

查看 CodePen 上 Taye A (@taye) 的 interact.js 像素彩虹画布

JavaScript 概述

var pixelSize = 16;

interact('.rainbow-pixel-canvas')
  .snap({
    // snap to the corners of a grid
    mode: 'grid',
    // specify the grid dimensions
    grid: { x: pixelSize, y: pixelSize }
  })
  .origin('self')
  .draggable({
    max: Infinity,
    maxPerElement: Infinity
  })
  // draw colored squares on move
  .on('dragmove', function (event) {
    var context = event.target.getContext('2d'),
        // calculate the angle of the drag direction
        dragAngle = 180 * Math.atan2(event.dx, event.dy) / Math.PI;

    // set color based on drag angle and speed
    context.fillStyle = 'hsl(' + dragAngle + ', 86%, '
                        + (30 + Math.min(event.speed / 1000, 1) * 50) + '%)';

    // draw squares
    context.fillRect(event.pageX - pixelSize / 2, event.pageY - pixelSize / 2,
                     pixelSize, pixelSize);
  })
  // clear the canvas on doubletap
  .on('doubletap', function (event) {
    var context = event.target.getContext('2d');

    context.clearRect(0, 0, context.canvas.width, context.canvas.height);
  });

  function resizeCanvases () {
    [].forEach.call(document.querySelectorAll('.rainbow-pixel-canvas'), function (canvas) {
      canvas.width = document.body.clientWidth;
      canvas.height = window.innerHeight * 0.7;
    });
  }

  // interact.js can also add DOM event listeners
  interact(document).on('DOMContentLoaded', resizeCanvases);
  interact(window).on('resize', resizeCanvases);

interact.maxInteractions(Infinity);

捕捉 用于修改指针坐标,使它们始终与网格对齐。

  .snap({
    // snap to the corners of a grid
    mode: 'grid',
    // specify the grid dimensions
    grid: { x: pixelSize, y: pixelSize }
  })

与之前的演示类似,启用了多个拖动,但需要更改额外的选项 maxPerElement,以允许在同一个元素上进行多个拖动。

  .draggable({
    max: Infinity,
    maxPerElement: Infinity
  })

移动角度使用 Math.atan2(event.dx, event.dy) 计算,并用于设置油漆颜色的色调。event.speed 用于调整亮度。

interact.js 具有轻触和双击事件,这些事件等效于单击和双击,但没有移动设备上的延迟。此外,与常规单击事件不同,如果鼠标在释放之前移动,则不会触发轻触。(我正在努力添加更多像这样的事件)。

  // clear the canvas on doubletap
  .on('doubletap', function (event) {
    ...

它还可以侦听常规 DOM 事件。在上面的演示中,它用于侦听窗口大小调整和文档 DOMContentLoaded。

  interact(document).on('DOMContentLoaded', resizeCanvases);
  interact(window).on('resize', resizeCanvases);

类似于 jQuery,它也可以用于委托事件。例如

interact('input', { context: document.body })
  .on('keypress', function (event) {
    console.log(event.key);
  });

提供元素尺寸

为了获取元素尺寸,interact.js 通常使用

  • Element#getBoundingClientRect() 用于 SVGElements,以及
  • Element#getClientRects()[0] 用于 HTMLElements(因为它包含元素的边框)

并添加页面滚动。这在检查要对元素执行的操作、检查放置、计算 'self' 原点以及其他一些地方时完成。如果您的应用程序保留了正在交互的元素的尺寸,那么使用应用程序的数据而不是获取 DOMRect 更有意义。为了允许这样做,Interactable 具有 rectChecker() [文档] 方法来更改获取元素尺寸的方式。该方法接受一个函数作为参数。当 interact.js 需要元素的尺寸时,该元素将传递给该函数,并使用返回值。

图形编辑器演示

下面的“SVG 编辑器”有一个 Rectangle 类,用于表示 DOM 中的 <rect class="edit-rectangle"/> 元素。每个矩形对象都有尺寸、用户看到的元素和一个绘制方法。

查看 CodePen 上 Taye A (@taye) 的 Interactable#rectChecker 演示

JavaScript 概述

var svgCanvas = document.querySelector('svg'),
    svgNS = 'http://www.w3.org/2000/svg',
    rectangles = [];

function Rectangle (x, y, w, h, svgCanvas) {
  this.x = x;
  this.y = y;
  this.w = w;
  this.h = h;
  this.stroke = 5;
  this.el = document.createElementNS(svgNS, 'rect');

  this.el.setAttribute('data-index', rectangles.length);
  this.el.setAttribute('class', 'edit-rectangle');
  rectangles.push(this);

  this.draw();
  svgCanvas.appendChild(this.el);
}

Rectangle.prototype.draw = function () {
  this.el.setAttribute('x', this.x + this.stroke / 2);
  this.el.setAttribute('y', this.y + this.stroke / 2);
  this.el.setAttribute('width' , this.w - this.stroke);
  this.el.setAttribute('height', this.h - this.stroke);
  this.el.setAttribute('stroke-width', this.stroke);
}

interact('.edit-rectangle')
  // change how interact gets the
  // dimensions of '.edit-rectangle' elements
  .rectChecker(function (element) {
    // find the Rectangle object that the element belongs to
    var rectangle = rectangles[element.getAttribute('data-index')];

    // return a suitable object for interact.js
    return {
      left  : rectangle.x,
      top   : rectangle.y,
      right : rectangle.x + rectangle.w,
      bottom: rectangle.y + rectangle.h
    };
  })

每当 interact.js 需要获取其中一个 '.edit-rectangle' 元素的尺寸时,它都会调用指定的 rectChecker 函数。该函数使用元素参数找到 Rectangle 对象,然后创建一个包含 leftrighttopbottom 属性的适当对象并返回它。

此对象用于在设置 限制元素矩形 选项时进行限制。在之前的滑块演示中,限制只使用指针坐标。在这里,限制将尝试阻止元素被拖出指定的区域。

  .inertia({
    // don't jump to the resume location
    // https://github.com/taye/interact.js/issues/13
    zeroResumeDelta: true
  })
  .restrict({
    // restrict to a parent element that matches this CSS selector
    drag: 'svg',
    // only restrict before ending the drag
    endOnly: true,
    // consider the element's dimensions when restricting
    elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
  })

矩形可拖动和可调整大小。

  .draggable({
    max: Infinity,
    onmove: function (event) {
      var rectangle = rectangles[event.target.getAttribute('data-index')];

      rectangle.x += event.dx;
      rectangle.y += event.dy;
      rectangle.draw();
    }
  })
  .resizable({
    max: Infinity,
    onmove: function (event) {
      var rectangle = rectangles[event.target.getAttribute('data-index')];

      rectangle.w = Math.max(rectangle.w + event.dx, 10);
      rectangle.h = Math.max(rectangle.h + event.dy, 10);
      rectangle.draw();
    }
  });

interact.maxInteractions(Infinity);

开发和贡献

我希望本文能很好地概述如何使用 interact.js 以及我认为它将对哪些类型的应用程序有用。如果不是,在 项目主页 上还有更多演示,您也可以在 TwitterGithub 上提出问题或发布问题。我真的很想制作一套全面的示例和文档,但我一直太忙于修复和改进。(我也太懒了 :-P)。

自 1.0.0 版本发布以来,用户评论和贡献带来了大量的错误修复和许多新功能,包括

因此请使用它、分享它、破坏它,并帮助使其变得更好!

关于 Taye Adeyemi

Web 开发人员,对用户交互和界面设计感兴趣。interact.js 的作者。目前在都柏林三一学院学习计算机科学,并通过参与开源项目、练习卡波耶拉以及学习意大利语和德语来保持头脑清醒。

Taye Adeyemi 的更多文章…

关于 Robert Nyman [荣誉编辑]

Mozilla Hacks 的技术布道者和编辑。进行关于 HTML5、JavaScript 和开放网络的演讲和博客。Robert 是 HTML5 和开放网络的坚定支持者,自 1999 年以来一直从事网络前端开发工作 - 在瑞典和纽约市。他还经常在 http://robertnyman.com 上发表博客,喜欢旅行和结识新朋友。

Robert Nyman [荣誉编辑] 的更多文章…


2 条评论

  1. Riccardo Forina

    非常好!

    只是一个小问题,滑块演示在 Chrome 中无法使用。我分叉了您的 Codepen 并进行了更新,使其在 Chrome 和 Firefox 中都能正常使用。我没有 IE,但它应该可以正常运行。以下是链接:http://codepen.io/anon/pen/WbNbqO

    干杯!

    2014 年 11 月 12 日 上午 08:47

    1. Taye Adeyemi

      谢谢!我已经复制了您的更改,现在应该可以正常工作了。

      2014 年 11 月 12 日 上午 09:01

本文的评论已关闭。