Sortable Grid — Drag & Drop on steroids using Vue.js

Maciej Kaczorowski
ergonode
Published in
4 min readSep 2, 2019

Drag & drop might be a nightmare for some reason, the API is pretty limited and the technology progress is way too fast

Facing business might be tough in many aspects, clients always want to reach impossible things so do I! Have you ever though to speed up dragging with GPU? The goal is to limit layout recalculations — they are way too expensive in real time applications.

Business requirements are to create interactive (kinda “sortable”) table with being able to reorder columns. To achieve it we are going to use display grid instead of table.

Whole project is written in Vue.js, it could be improved with Vuex/EventBus to reduce emitting hell.

Both versions are available at Github CPU | GPU! Enjoy :)

Logic introduction

  • Grid is rendered based on data model passed as a prop: columns
  • Each column is divided into two halves — isBefore flag
  • To limit column reordering we are checking index + 1 and index -1 to prevent updating when dragged column has sibling. To illustrate that we can consider a simple cases: dragged column is at index 1 and the mouse is on first half (isBefore = true) of column with index 2; dragged column is at index 1 and the mouse is on second half (isBefore = false) of column with index 0.

CPU Solution

The first thing which comes to mind would be using only CPU by switching dragged element position with element under dragged. Let’s go through the most common problems.

  • When we drag any element browser engine is creating copy/bitmap (that’s only the theory — could not find any thing about it in official documentation). We cannot style it — I mean we can… but it will affect both of the elements — original and dragged one made by browser engine.
  • Solution for this is to create element copy and attache it as an image to dragged event. We also need to append the cloned element to let say document body — it can be anything which is under the parent element.
  • Next step would be to do actual ‘sortable’ thingy — I will enable paint flashing from chrome dev tools to present layout recalculations.

As we can see whenever we swap dragged element with element under the layout is going to be recalculated, what’s more — we cannot do efficient animation!

We can do better — don’t we!?

GPU Solution — Recommended

First thing! The problem cannot be solved only by using GPU — the elements has to be rendered somehow as well as creating clone of dragged element. We can only improve the user experience interaction after we did start drag by using transform approaches.

  • That’s the part where we do need to complicate our algorithm.. Whenever we do ondragover we need to update dragged column transform along with sibling column to mimic swapping data model positions in array.
  • We need to keep a track of dragged element index — the original position of element is going to be the position from where we did start our drag. Along with drag we need to update fixed dragged index to know where the element actually is.
  • As we did meet all of the conditions we can see the big improvement of our “sortable” grid:

No flashing baby!! Yaay!

Conclusion

The first scenario by using only CPU operations seems much more easier to understand, there is much more less things that you have to take care of — honestly two things! Swapping models in array with given indexes and the algorithm which determinate position of mouse above the column. By implementing transform calculations in second solution the complexity has grown. It requires much more logic to implement. Indexing system which was taken care by browser engine has to be made from scratch to keep the track of transformed elements.

Tips

  • Consider scenario: You really want to remove element ondragstart — seems natural, but the HTML5 API does not provide functionality which would allow us to make that happen. Whenever you remove element from your data model which rendering is based on — the ondragend event is going to fire, you may ask your self “but why?”. Well… you did mutation on your array, the DOM is re-rendered and the result array does not have the data which you removed… garbage collector will clear the events which were attached to it and ye, our dream for having nice modern drag & drop when you “drag” element instead of creating copy of is gone…Not on my watch! You can do it! You need to hack API a bit :D, basically you need to timed out removal action a bit by requesting animation frame or setting time out.
  • Similar scenario from above. You might render different content of dragged element. Conditions! Parent (dragged element) has dynamic size, the default size is equal (width = 0, height = 0), content is determining the size. Whenever you put the content inside slot rendered by condition v-if=”isDragged” you may meet same issue, the event ondragend will be called immediately, because element has no size — weird (but, yeah… that’s happening). To prevent that, you may add a class to parent which will set a size of it, or render different component inside of it. Remember about callback block under.

Warning! You need to save removed data model somewhere and add it to the array inside call of ondragend otherwise you will not have correct data structure — removed element will be gone forever.

window.requestAnimationFrame(() => {
// Remove data from model at index
});

Feel free to ask any question or post a feedback! Hope you like it! I did implement more complex solution in Open Source PIM Ergonode. Enjoy!

--

--

Maciej Kaczorowski
ergonode
Editor for

I am hard working with full programming passion. My goal is to step forward with doing that what I love to do.