5
\$\begingroup\$

This is a very simple implementation of dragging elements in html/js. I'm working on a web application that could have thousands of draggable elements on screen at any given time.

Currently, this code works perfectly as shown below (with only one draggable element), however if I copy and paste that element (<span class="draggable">This is draggable</span>) a few thousand times (I've been using around 4000 for testing) page load is significantly slower, and the framerate slows considerably.

I can understand the page load slowing due to the number of elements, but is there anything that can be done to ensure the frame rate remains smooth while dragging an element given large numbers of draggable elements in the DOM?

Note - The implementation here deliberately ignores various issues like scrolling and so on that I will be implementing later, and I believe will have a largely negligible effect on performance. If that's an incorrect assumption please let me know.

dragdrop.html

<!doctype html>
<html>
    <head>
        <title>Drag + Drop</title>
        <link rel="stylesheet" type="text/css" href="dragdrop.css" />
    </head>
    <body>
        <div id="draggableItemContainer" class="noselect">
            <span class="draggable">This is draggable</span>
        </div>
        <script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
        <script type="text/javascript" src="dragdrop.js"></script>
    </body>
</html>

dragdrop.css

span {
    position:absolute;
    background: #FF8888;
    cursor: pointer;
}

.noselect {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

dragdrop.js

var dragDrop = (function() {
    var elementBeingDragged;
    var clickPointOffsetX;
    var clickPointOffsetY;

    function _dragging(e) {
        elementBeingDragged.style.left = (e.clientX - clickPointOffsetX) + "px";
        elementBeingDragged.style.top = (e.clientY - clickPointOffsetY) + "px";
    }

    function _stopDrag(e) {
        document.removeEventListener('mousemove', _dragging);
        document.removeEventListener('mouseup', _stopDrag);
    }

    function startDrag(e) {
        elementBeingDragged = this;
        document.addEventListener('mouseup', _stopDrag);
        document.addEventListener('mousemove', _dragging);

        var rect = elementBeingDragged.getBoundingClientRect();

        clickPointOffsetX = e.clientX - rect.left;
        clickPointOffsetY = e.clientY - rect.top;
    }

    var publicAPI = {
        startDrag: startDrag
    };

    return publicAPI;
})();

$(function() {
    $('#draggableItemContainer').on('mousedown', '.draggable', dragDrop.startDrag);
});
\$\endgroup\$
  • \$\begingroup\$ Interestingly, different browsers have different performance characteristics. Internet Explorer, for all its flaws, seems to handle it fine no matter how many elements you add to the page. Is there a particular browser for which you wish to optimize the code? \$\endgroup\$ – Thriggle Feb 2 '17 at 17:38
  • \$\begingroup\$ Might be related: Optimizing native hit testing of DOM elements (Chrome) Also, FWIW, a canvas or svg might be a better choice. \$\endgroup\$ – wOxxOm Feb 3 '17 at 5:14
  • \$\begingroup\$ @Thriggle - I'm seeing issues with 4,000 elements in every browser - including IE11 - on my work laptop (i5, 4gb), maybe that's just weaker pc specs causing the issue? I'll try to have a look on my more powerful machine at home this evening and see if there's a difference. \$\endgroup\$ – Hecksa Feb 3 '17 at 9:21
  • \$\begingroup\$ @wOxxOm - thanks for the link. Yes, canvas or svg would almost certainly be better, unfortunately they're not really options I can go with for various reasons. \$\endgroup\$ – Hecksa Feb 3 '17 at 9:25
  • \$\begingroup\$ @Hecksa Good call; I checked on a 4GB machine and IE11 had the same problems. Interesting that on an 8GB machine, Chrome and Firefox run into lag issues long before IE does. \$\endgroup\$ – Thriggle Feb 3 '17 at 14:15
4
\$\begingroup\$

Here are some suggestions to improve the fluidity of your app:

Use translate instead of top/left positioning

According to this blog post: https://www.paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft/, translate is much more efficient regarding the CPU usage

Use requestAnimationFrame

https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame

This function allows you to do the computation only if required (see this SO answer on how to combine it with mousemove: https://stackoverflow.com/a/23658815/3207406)

\$\endgroup\$
1
\$\begingroup\$

I honestly question your use case. Can you expect a user to realistically interact with thousands of elements in any given view? Should you be considering pagination or some other means to allow the user to interact with smaller groupings of these element Also, just because you have attached an event listener that could possibly impact a number of elements, the user is only going to be dragging one element at a time I presume, so I am not sure you are going to have to worry about the performance of the animation to any great extent.

\$\endgroup\$
  • 4
    \$\begingroup\$ I accept your point that the use case is difficult to see. However, I am not asking whether having thousands of draggable elements on a page is a good idea, or usable. I accept it isn't. I'm simply asking how I can make a page perform given those requirements. As for the animation performance, yes, I'm only dragging one element at a time, and have seen animation performance issues with thousands of elements using the code above, which is why I asked the question. \$\endgroup\$ – Hecksa Feb 2 '17 at 14:57
  • \$\begingroup\$ @Hecksa Unfortunately, this sort of animation will necessarily trigger reflows such that you are causing layout thrashing. I am guessing it is not the animation that is the problem, but rather the layout thrashing. You should look in browser tools to see if you can identify where the time is being spent for your use case. A good resource - gist.github.com/paulirish/5d52fb081b3570c81e3a \$\endgroup\$ – Mike Brant Feb 2 '17 at 22:47
  • \$\begingroup\$ Thanks for the link. I'll take another look in dev tools with that in mind. \$\endgroup\$ – Hecksa Feb 3 '17 at 9:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.