Drag element and move it around - performances and animation

Hello all !

So I’m trying to have a HTML element dragged around the page with my finger. The element is hidden at first, three other are shown, and when dragging one of them the hidden element reveals and moves with the finger - the initial element stays in place. When released, the dragged element disappears.

So here is what I did :

HTML :

<div on-drag="onDrag($event)"  on-release="onRelease($event)" id="dragger_1" class="large greenbg" ng-class="{shadowClass: doshadow==1}">drag 1</div>
<div on-drag="onDrag($event)"  on-release="onRelease($event)" id="dragger_2" class="large redbg" ng-class="{shadowClass: doshadow==2}">drag 2</div>
<div on-drag="onDrag($event)"  on-release="onRelease($event)" id="dragger_3" class="large bluebg" ng-class="{shadowClass: doshadow==3}">drag 3</div>

<div id="dragged" class="mini" ng-class="{hidden: doshadow == 0}" ng-style="draggedStyle">dragged</div>

Controller :

myApp.controller("playerCtrl", ["$stateParams", "$scope", function($stateParams, $scope) {
    $scope.player = $stateParams.playerId;

    $scope.doshadow = 0;
    $scope.draggedStyle = {};

    $scope.onDrag = function(event)  {
        //console.log('Reporting : drag');
        console.log(event.target.className);

        $scope.doshadow = event.target.id.substr(8, 1);

        $scope.draggedStyle = {
            'left': '30px',
            'top': '50px'
        };
    }
    $scope.onRelease = function(event)  {
        //console.log(event.target);

        $scope.doshadow = 0;
        $scope.draggedStyle = {};
    }
}]); 

So this works perfectly… The only thing that I still need to do - and don’t know how - is to fix the position according to the movement, and not fix it to 30px / 50px as it’s currently done.

So two things :

  • Am I doing things right ?
  • Can you help me figure out something with this mouse issue ?

I’m a beginner with Angular, and getting to this took me 6 hours of work :smile:

Thank you very much ahead !

Okay, found it :

$scope.draggedStyle = {
    'left': event.gesture.center.pageX + 'px',
    'top': event.gesture.center.pageY + 'px'
};

Thanks anyway :smile:

2 Likes

Actually, for better performance and smoother animation, use this :

$scope.draggedStyle = {
    "transform": "translate(" + event.gesture.center.pageX + "px, " + event.gesture.center.pageY + "px)",
    "-webkit-transform": "translate(" + event.gesture.center.pageX + "px, " + event.gesture.center.pageY + "px)"
};

Translation is much better than positionning, as explained here : http://www.paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft/

It’s much smoother that way, actually, but still a little bit bothering me as not really in par with native performances (as seen in Tera Battle for example). So if one of you has advices on how to improve performances, I would gladly use it ! Thanks !

2 Likes

A) Method to manipulate the DOM-element
You are using Angular ng-style to update the position of the element, pixel by pixel.
This can be costly performance-wise. You might want to try some other route. One example is below, #3: have Velocity move the element by translateX and translateY for drag events.

Drag events themselves:
One way to link drag to controller-side is to use onDrag as you did - another is to register $ionicGesture ( http://ionicframework.com/docs/api/service/$ionicGesture/ ) to listen on an element:

$ionicGesture.on(eventType, callback, $element)

Event types include dragstart drag dragend and many more (hold, tap, doubletap, drag, dragstart, dragend, dragup, dragdown, dragleft, dragright, swipe, swipeup, swipedown, swipeleft, swiperight, transform, transformstart, transformend, rotate, pinch, pinchin, pinchout, touch, release).

Optimization
Another point: at the very least - you do not need to update the style 60 times a second. This is what is currently happening in your code. Hammer.js (used by Ionic for the touch events) is pounding the events at 60 fps and you are updating the $scope.draggedStyle at that rate, then as well…
However you end up manipulating the DOM-element position, you could always drop a few events (every other or so) by code. Made a huge difference with some mobile devices in the project I am working on now. It is not visible at the Github code below, I added it afterwards for a specific (lots of large image elements) case.
I added this hack to skip every other update:

        $ionicGesture.on('drag transform', function(ev) {
            if(skipThisFrame){
                skipThisFrame = false;
                return;
            }
            skipThisFrame = true;

B) Then, the actual manipulation of the position:

I suggest you benchmark a few ways - using your own specific cases, as controversial information exists about which method currently is the fastest.

1. CSS and transform
Previously, the recommended trick when using transforms was to always use translate3D to force hardware acceleration. Supposedly plain transform: translate is not always triggering hw acceleration.
Things are different on different platforms - desktop Chrome on OSX was fine using translate: transform(x, y) just a few months ago, but some update changed everything once again and had to switch back to using translate: transform3D(X, Y, 1px).
You might also experiment with backface visibility to eliminate flicker, if any is visible:

-webkit-backface-visibility: hidden; /* Chrome, Safari, Opera */
backface-visibility: hidden;

Solution: TEST using transform: translate3D(x, y, 0px) – or 1px or whatever for Z.

2. CSS + left / top
Some benchmarks proved little or no difference when using left / top vs. translate vs. translate3D. Especially noteworthy was, that with certain setups, I got better performance using left / top. …But mostly on desktop browsers. On some mobile browsers the performance was very bad, so switched back to use #1 again after a few disappointments.

3.Libraries
I have had decent success with Velocity ( http://velocityjs.org/ ). You can use Velocity to animate a movement with easings – or you can use it to follow the drag px by px using mock = true / or an easing time of 0. Velocity animations are automatically hardware accelerated on mobile devices, so no need to use transforms for Z there.

I ended up using Velocity, wrapped a generic drag directive around it - enabling dragging X to cause translate X - or anything else (like rotate Y etc.). Also - it enabled me to add a momentum to the drag movement: as you release the element, the end movement is bouncy (spring-like) or easing out (the usual). The movement plus the possible endpoints may have limits, so the dragged element snaps to some points. This can be used to return the dragged element to the start point with a spring-effect, has it not been dragged far enough.

7 Likes

Absolutely fu***** awesome, man ! You gave me so much to explore right now, I could not thank you enough. I will continue working on the project, get it to a more advanced state ,and then make tests following your recommandations - bookmarked with love, of course. Again, thank you so much. Couldn’t hope for a more clear and complete answer !

One more question, friend : have you tried using HTML5 canvas, to see if it performs better ?

Thanks jerryBels ! u helps me lots !

1 Like

Did you ever figure out the best way to achieve this?

Sorry buddy didn’t see that question before now… I actually moved to another project for a while, so this project is “in wait”. Sorry I can’t help more.