lag while dragging element

Viewed 1562

I am implementing a functionality where I can add, drag, and delete "sticky notes" on a webpage. The app is built in Vue js but it also renders content in an iframe. All the notes that are added to the page have to be on top of the iframe (which is almost 80% of the main .vue page), therefore positioning here is important and I also have to preserve the positions because I must render the notes on the same position on next page reload. The problem is that there's a lot of lag in dragging the "note" element. The "sticky note" itself is a very lite weight separate component.
I have noticed that this because of the iframe that's present on the vue page because when I inspect the DOM in the browser and delete the iframe and then try dragging the "note" component then it works smoothly.

Things I have tried:

  1. Use throttling: I tried to use loadsh.throttle but that does not make any difference.
  2. Inject "sticky note" code inside the iframe: I tried to inject a sticky note element into the iframe and append it to iframe's body. It actually makes the dragging very smooth. But I don't want to go ahead with this solution because then I will have to write a lot of extra code to maintain the state of multiple notes (which can be done easily with Vue js). In this solution, instead of using the document of the main page to attach events for dragging, I attached all the events to iframe.contentDocument.

So the question here is how do I make the drag smooth while using vue.js
Sandbox Link: https://codesandbox.io/s/affectionate-jang-3c1hw?file=/src/components/HelloWorld.vue

of the lag when dragging

In this gif, the background is actually the iframe, I have reduced its opacity to hide it.
I could not actually include the iframe in sandbox code because of cross-origin problems but I have included a lot of extra content in that sandbox to make it heavy.

UPDATE: Using the chrome task manager, I found that the page is only taking max 200MB memory and the GPU process is taking another 200MB. I am running this on a system with 16GB RAM. So I don't think it's a memory issue. But there is a sudden spike in the CPU consumption when I start dragging the element (up to 40%).

UPDATE:
I have found the fix for this problem. The actual problem wasn't lag but it was mouse trailing i.e. the draggable element wasn't able to catch up with the fast moving mouse cursor. And the slowness was due to e.preventDefault in the dragMouseDown method inside Note.vue. Just removing the e.preventDefault fixed all the problems. Also just adding a return false at the end of dragMouseDown method seems to cause the same amount of lag.

function dragMouseDown(e) {
     e = e || window.event;
     
     // e.preventDefault(); --> this line causes the mouse trailing issue
   
     document.onmouseup = closeDragElement;     
     document.onmousemove = elementDrag;

     // return false; --> adding this line also causes mouse trailing problem.
}

So for now I have just removed the preventDefault from this function. But I tried searching and could not find any explanation of this behavior. Also I am not sure if not cancelling the event can cause any other issue.

2 Answers

The problem is that you're working with mousemove without using requestAnimationFrame to debounce. Here's a working example using debounce (CodeSandbox link)

Generally, anytime you're trying to animate with javascript, you want to use requestAnimationFrame. Another thing you could try is using transform to change the position of the element instead of absolute positions.

Here's the MDN reference on requestAnimationFrame.

Here's an article by Paul Irish about using transform instead of absolute positioning to speed up a drag/drop.

As your extra description in the comments,

It starts to lag when these a lot of other content on screen. There's a v-for in the sandbox. make it do more iterations and it will start to laggy

The problem you met is the Dom elements were too many on your page then caused high memory usage. (Actually I tried <div v-for="i in 10000" :key="i">", then it took around 3GBs of memory), finally, everything works slowly and laggy

If your page has tons of Dom elements, you may have to consider dynamically add only visible items into the Dom tree when scrolling.

Even there are some packages that already implements this feature.

Below is one demo which uses RecycleScroller of vue-virtual-scroller:

100K items in the Codepen

You will see even the number of the items are 100,000, it still works smoothly.

PS: you may notice the below statement in the user guide of the above package=vue-virtual-scroller

The browsers have a size limitation on DOM elements, it means that currently the virtual scroller can't display more than ~500k items depending on the browser.

Related