keras 香草编码器_完善纯香草javascript中的拖放

本文介绍如何在纯JavaScript中实现拖放功能,包括混合输入事件支持、跨浏览器兼容性和性能优化。文章覆盖了从基本事件监听到高级动画平滑处理的全过程。

keras 香草编码器

Drag-and-drop functionality is the bread and butter of a modern web UX. It’s an aspect of the API, part of the HTML standard. Lots of UI libraries provide it out of box.

拖放功能是现代Web UX的基础。 这是API的一个方面 ,是HTML标准的一部分。 许多UI库都是开箱即用的。

Sometimes, though, we have to deal with situations where neither the standard API nor the libraries can help, and we have to implement the functionality ourselves. In this article we’ll do exactly that—we’ll implement:

但是,有时候,我们必须处理标准API和库都无法提供帮助的情况,而我们必须自己实现功能。 在本文中,我们将完全做到这一点-我们将实现:

  • Cross-input (with blended mouse and touch-events support)

    交叉输入(具有混合的鼠标和触摸事件支持)
  • Cross-browser friendly

    跨浏览器友好
  • Performant (60 FPS)

    性能(60 FPS)
  • Efficient (no wasted JavaScript processing)

    高效(不浪费JavaScript处理)

1.先决条件 (1. Prerequisites)

For the sake of this article, we’re going to be dragging a few black square boxes (divs) around a container (div) element in a simple HTML page:

为了本文的目的,我们将在一个简单HTML页面中的容器(div)元素周围拖动几个黑色方框(divs):

<div class="container">
  <div style="top: 100px; left: 100px" class="box"></div>
  <div style="top: 200px; left: 200px" class="box"></div>
  <div style="top: 300px; left: 300px" class="box"></div>
  <div style="top: 400px; left: 400px" class="box"></div>
</div>
.container {
  width: 600px;
  height: 600px;
  background-color: darkgrey;
  touch-action: none;
}
.box {
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: black;
}

Nothing fancy here, but note we’re applying the touch-action: none; CSS rule to prevent any browser level manipulations (such as scroll or zoom) for events inside the container.

这里没什么好看的,但请注意,我们正在应用touch-action: none; CSS规则 ,可防止对容器内的事件进行任何浏览器级别的操作(例如,滚动或缩放)。

2.添加输入监听器 (2. Adding Input Listeners)

Image for post
Pointer events provide the blender supporting both input types
指针事件提供了支持两种输入类型的混合器

Since we want to support cross-input, we could write some detection mechanism for whether the user is using touch input or mouse input, but in the modern world, the user might be using both input types at the same time (touchscreen laptop, anyone?).

因为我们要支持交叉输入,所以我们可以编写一些检测机制来确定用户是使用触摸输入还是鼠标输入,但是在现代世界中,用户可能同时使用两种输入类型 (触摸屏笔记本电脑,有人吗?)。

Hence, we should aim to support both. One way would be to attach both types of listeners, but a better way is to leverage the Pointer Events API — the blender of two input types that nowadays has decent support by all of the major browsers.

因此,我们应该努力支持两者。 一种方法是连接两种类型的侦听器,但更好的方法是利用Pointer Events API-两种输入类型的混合器,如今,所有主要浏览器都提供了不错的支持。

Our drag-and-drop user experience can be mapped this way:

可以通过以下方式映射我们的拖放式用户体验:

  • User points at a box, presses the mouse button/screen (pointerdown)

    用户指向一个框,按下鼠标按钮/屏幕( pointerdown )

  • User drags the box by moving the mouse/their finger across the screen (pointermove)

    用户通过在屏幕上移动鼠标/手指来拖动框( pointermove )

  • User drops the box by releasing the mouse/their finger from the screen (pointerup/pointercancel*)

    用户通过从屏幕上释放鼠标/手指来pointerup pointercancel ( pointerup / pointercancel *)

*Touch devices feature an additional touchcancel event — for the cases when the touch event gets disrupted (for example, when there are too many touch points on the screen).

*触摸设备还具有一个附加的touchcancel事件-在触摸事件中断的情况下(例如,当屏幕上的触摸点过多时)。

So let’s write some JavaScript and add our event listeners:

因此,让我们编写一些JavaScript并添加事件监听器:

const container = document.querySelector('.container');
container.addEventListener('pointerdown', userPressed, { passive: true });


function userPressed(event) {
  element = event.target;
  if (element.classList.contains('box')) {
    console.log("pick up!")
    container.addEventListener('pointermove', userMoved, { passive: true });
    container.addEventListener('pointerup', userReleased, { passive: true });
    container.addEventListener('pointercancel', userReleased, { passive: true });
  };
};


function userMoved(event) {
  console.log("dragging!")
};


function userReleased(event) {
  console.log("dropped!")
  container.removeEventListener('pointermove', userMoved);
  container.removeEventListener('pointerup', userReleased);
  container.removeEventListener('pointercancel', userReleased);
};

一些注意事项 (A few notes)

  • We’re only attaching the pointerdown event to the container and adding/removing other listeners dynamically to prevent event pollution — we’re using listeners on an as-needed basis, keeping things tight.

    我们只是将pointerdown事件附加到容器上,并动态添加/删除其他侦听器以防止事件污染—我们在需要时使用侦听器,以保持紧密状态。

  • The events are attached to the container, as we’re using event delegation.

    事件被附加到容器,因为我们正在使用事件委托

  • We’re also using the passive flag to ensure our events don’t interfere with our scrolling events.

    我们还使用passive标志来确保事件不会干扰滚动事件。

3.使盒子移动 (3. Making Boxes Move)

Our boxes feature absolute positioning and rely on left/top properties to be positioned inside of the container. Let’s add functionality to actually move the boxes around:

我们的盒子具有absolute定位功能,并依靠left / top属性在容器内部定位。 让我们添加功能以实际移动框:

const container = document.querySelector('.container');
container.addEventListener('pointerdown', userPressed, { passive: true });


var element, bbox, startX, startY;


function userPressed(event) {
  element = event.target;
  if (element.classList.contains('box')) {
    startX = event.clientX;
    startY = event.clientY;
    bbox = element.getBoundingClientRect();
    container.addEventListener('pointermove', userMoved, { passive: true });
    container.addEventListener('pointerup', userReleased, { passive: true });
    container.addEventListener('pointercancel', userReleased, { passive: true });
  };
};


function userMoved(event) {
  let deltaX = event.clientX - startX;
  let deltaY = event.clientY - startY;
  element.style.left = bbox.left + deltaX + "px";
  element.style.top = bbox.top + deltaY + "px";
};


function userReleased(event) {
  container.removeEventListener('pointermove', userMoved);
  container.removeEventListener('pointerup', userReleased);
  container.removeEventListener('pointercancel', userReleased);
};

There are now more things going on here:

现在,这里还有更多事情要做:

  • Inside the userPressed event handler, we capture the coordinates of the starting point for the movement as well as the box element’s BoundingClientRect; both will help to calculate the amount of movement we need to apply to the box based on the pointer’s distance covered during the move.

    userPressed事件处理程序内部,我们捕获了运动起点的坐标以及box元素的BoundingClientRect ; 两者都将有助于根据移动过程中指针所经过的距离来计算我们需要应用于框的移动量。

  • Inside the userMoved handler, we leverage startX and startY data to calculate the delta for which the box should visually move in relation to its previous position.

    userMoved处理程序内部,我们利用startXstartY数据来计算框应相对于其先前位置在视觉上移动的delta

Now the user can, in fact, move the boxes and do so using touch or mouse input.

现在,用户实际上可以移动盒子并使用触摸或鼠标输入进行移动。

4.调优性能 (4. Tuning Performance)

There are a few known approaches we can take to improve performance. In this simplistic case, we don’t perform any heavy calculations; hence, even as is the performance looks fine. But we can consider the following options:

我们可以采取几种已知的方法来提高性能。 在这种简单的情况下,我们不执行任何繁重的计算; 因此,即使性能看起来也不错。 但是我们可以考虑以下选择:

'transform3d' (‘transform3d’)

This trick might be out of date nowadays, as modern browsers learned to optimize for layout changes, but it’s still useful to know that instead of directly modifying our box’s left and top positions, we could apply transform during pointermove and only apply actual style changes on pointerup:

由于现代浏览器已学会优化布局更改,因此这种技巧可能已不合时宜,但了解而不是直接修改框的lefttop位置,我们可以在pointermove期间应用transform并仅对pointerup

const container = document.querySelector('.container');
container.addEventListener('pointerdown', userPressed, { passive: true });


var element, bbox, startX, startY, deltaX, deltaY;


function userPressed(event) {
  element = event.target;
  if (element.classList.contains('box')) {
    startX = event.clientX;
    startY = event.clientY;
    bbox = element.getBoundingClientRect();
    container.addEventListener('pointermove', userMoved, { passive: true });
    container.addEventListener('pointerup', userReleased, { passive: true });
    container.addEventListener('pointercancel', userReleased, { passive: true });
  };
};


function userMoved(event) {
  deltaX = event.clientX - startX;
  deltaY = event.clientY - startY;
  element.style.transform = "translate3d("+deltaX+"px,"+deltaY+"px, 0px)";
};


function userReleased(event) {
  container.removeEventListener('pointermove', userMoved);
  container.removeEventListener('pointerup', userReleased);
  container.removeEventListener('pointercancel', userReleased);
  element.style.left = bbox.left + deltaX + "px";
  element.style.top = bbox.top + deltaY + "px";
  element.style.transform = "translate3d(0px,0px,0px)";
};

'RequestAnimationFrame' (‘RequestAnimationFrame’)

We can also achieve smoother animation while dragging (~60 FPS) through the use of the requestAnimationFrame API:

通过使用requestAnimationFrame API,我们还可以在拖动(〜60 FPS)的同时实现更流畅的动画:

const container = document.querySelector('.container');
container.addEventListener('pointerdown', userPressed, { passive: true });


var element, bbox, startX, startY, deltaX, deltaY, raf;


function userPressed(event) {
  element = event.target;
  if (element.classList.contains('box')) {
    startX = event.clientX;
    startY = event.clientY;
    bbox = element.getBoundingClientRect();
    container.addEventListener('pointermove', userMoved, { passive: true });
    container.addEventListener('pointerup', userReleased, { passive: true });
    container.addEventListener('pointercancel', userReleased, { passive: true });
  };
};


function userMoved(event) {
  // if no previous request for animation frame - we allow js to proccess 'move' event:
  if (!raf) {
    deltaX = event.clientX - startX;
    deltaY = event.clientY - startY;
    raf = requestAnimationFrame(userMovedRaf);
  }
};


function userMovedRaf() {
  element.style.transform = "translate3d("+deltaX+"px,"+deltaY+"px, 0px)";
  // once the paint job is done we 'release' animation frame variable to allow next paint job:
  raf = null;
};


function userReleased(event) {
  container.removeEventListener('pointermove', userMoved);
  container.removeEventListener('pointerup', userReleased);
  container.removeEventListener('pointercancel', userReleased);
  // if animation frame was scheduled but the user already stopped interaction - we cancel the scheduled frame:
  if (raf) {
    cancelAnimationFrame(raf);
    raf = null;
  };
  element.style.left = bbox.left + deltaX + "px";
  element.style.top = bbox.top + deltaY + "px";
  element.style.transform = "translate3d(0px,0px,0px)";
};

这里有几点注意事项 (A few notes here)

  • We added the raf variable to helps us debounce pointermove events with help from the requestAnimationFrame timer, as we don’t want JavaScript to perform calculations that won’t be used since the animation frame was already scheduled and hasn’t yet completed.

    我们添加了raf变量,以帮助我们在requestAnimationFrame计时器的帮助下对pointermove事件进行反跳,因为我们不希望JavaScript执行由于已经安排好动画框架且尚未完成而不会使用的计算。

  • We made sure the pointermove event, which fires many times per second, is lightweight and only contains the logic necessary to obtain the coordinates information and triggers the style change.

    我们确保了pointermove触发多次的pointermove事件是轻量级的,并且仅包含获取坐标信息并触发样式更改所需的逻辑。

3. CSS规则:“将改变”和“包含” (3. CSS Rules: ‘will-change’ and ‘contain’)

In addition to the above, there are CSS rules that you could explore to tune performance further.

除了上述内容,您还可以探索CSS规则来进一步调整性能。

The will-change CSS rule indicates to your browser the element’s attributes are expected to change and allows the browser to perform additional optimizations. But as the documentation says — use it with caution:

will-change CSS规则指示浏览器元素的属性都有望改变,并允许浏览器执行额外的优化。 但正如文档所述,请谨慎使用:

“Important: will-change is intended to be used as a last resort, in order to try to deal with existing performance problems. It should not be used to anticipate performance problems.”

“重要: will-change是不得已的手段,目的是试图解决现有的绩效问题。 它不应该被用来预测性能问题。”

.box {
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: black;
  will-change: transform, left, top;
  contain: layout;
}

The contain CSS rule isn’t fully supported by all browsers yet but can help indicate that a particular DOM’s element style or layout changes won’t affect another page’s elements and thus won’t trigger potential expensive reflows.

contain CSS规则没有完全被所有的浏览器,但还可以帮助支持表示一个特定的DOM的元素风格或布局的变化不会影响其他页面的元素,因而不会触发潜在的昂贵的回流。

It isn’t really adding any value in our simplistic case (given that our boxes are positioned as absolute, but it’s a useful trick in more complex UI and dragging situations. You can watch this amazing video for more details.

在我们的简单案例中,它并没有真正增加任何价值(假设我们的盒子定位为absolute ,但这在更复杂的UI和拖动情况下是一个有用的技巧。您可以观看此精彩视频 ,了解更多详细信息。

结论 (Conclusion)

We went through the basics of how drag-and-drop functionality can be implemented in pure JavaScript, and we outlined a few techniques and directions for performance optimization. The main things to remember:

我们介绍了如何在纯JavaScript中实现拖放功能的基础知识,并概述了一些性能优化的技术和方向。 要记住的主要事项:

  • Leverage blended input events to ensure hybrid devices are covered.

    利用混合输入事件来确保涵盖混合设备。
  • Attach/detach move/up/cancel events dynamically.

    动态附加/分离move / up / cancel事件。

  • Do as much of the heavy weight lifting upfront or inside the down and up handlers, but keep the move handler lightweight.

    做尽可能多的重型举重前期的或内部downup处理程序,但保持move处理器轻巧。

  • Leverage requestAnimationFrame for debouncing and smooth animation while dragging.

    利用requestAnimationFrame在拖动时进行反跳和平滑动画处理。

  • Explore CSS rules that can help gain additional performance.

    探索有助于提高性能CSS规则。

Full implementation here:

完整的实现在这里:

Thank you for reading the article, and happy coding!

感谢您阅读本文,并祝您编程愉快!

翻译自: https://medium.com/better-programming/perfecting-drag-and-drop-in-pure-vanilla-javascript-a761184b797a

keras 香草编码器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值