r3plica
r3plica

Reputation: 13397

Animation speed issue

I have this slider, when I click one of the dots it slides to the center of the screen. The problem I have is that if I click a dot that is already close to the center, it moves very slowly. If I click a dot that is much further away, it takes the same amount of time to reach the center (but appears to be moving faster; obviously this is an illusion). What I would like to do is for them all to travel at the same speed. So the further one away takes longer to get to the center, but the closest one is must faster.

Currently my code looks like this:

// Moves the current active item to the center
moveToCenter: function (e, dragOptions, animate) {

    // Get the central position 
    var center = $window.innerWidth / 2,
        rect = e.target.getBoundingClientRect(),
        distance = center - rect.left - ((50 - 18) / 2), // Make these variables?
        positive = rect.left > center;

    // If we should animation
    if (animate) {

        // Get our fake mouse position
        var clientX = center,
            target = center + distance,
            offset = clientX / $window.innerWidth,
            length = distance / $window.innerWidth;

        // Initiate the first move
        _setupMove(dragOptions, offset);

        // Execute every 1 millisecond
        var timer = $interval(function () {

            // Get our new clientX
            clientX = clientX + length;
            offset = clientX / $window.innerWidth;

            // Move
            _moveElements(dragOptions, offset, service.updateDragElement);

            // Should we stop?
            var stop = positive ? clientX <= target : clientX >= target;

            // If we should stop
            if (stop) {

                // Cancel the interval
                $interval.cancel(timer);
            }
        }, 1);

        // Otherwise
    } else {

        // Update the current position
        dragOptions.currentX = distance / dragOptions.speed;

        // Move our elements
        service.updateDragElement(dragOptions);
    }
},

I know it's to do with setting clientX. I thought that maybe I needed to work out how far I should travel based on time, so I created this:

// Get our fake mouse position
var clientX = center,
    target = center + distance,
    offset = clientX / windowWidth,
    percent = distance / windowWidth,
    interval = 1000 * percent;

// Initiate the first move
_setupMove(dragOptions, offset);

// Execute every 1 millisecond
var timer = $interval(function () {

    // Get our new clientX
    clientX = clientX + (distance * percent);
    offset = clientX / windowWidth;

    // Move
    _moveElements(dragOptions, offset, service.updateDragElement);

    // Should we stop?
    var stop = positive ? clientX <= target : clientX >= target;

    // If we should stop
    if (stop) {

        // Cancel the interval
        $interval.cancel(timer);
    }
}, interval);

In theory it should travel at the same speed regardless of the distance it has to travel. But this didn't work.

Does anyone have some idea how I can solve this?


I have changed my code to this thanks to the proposed answer:

// Animates the slider
animate: function (startTime, distance, duration, options) {

    // Animate
    requestAnimationFrame(function () {

        // Get our offset
        var now = Date.now(),
            timeDelta = Math.min(now - startTime, duration),
            timePercent = timeDelta / duration,
            offset = timePercent * distance;

        var finalX = options.finalX + offset;

        // Create the transform
        options.transform = 'translateX(' + finalX + 'px)';

        // Add to our element
        options.element.style.transform = options.transform;

        if (timeDelta < duration) {
            service.animate(startTime, distance, duration, options);
        } else {
            options.finalX = finalX;
            options.currentX = options.xOffset + (distance / $window.innerWidth);
        }
    });
},

// Moves the current active item to the center
moveToCenter: function (e, dragOptions, animate) {

    // Get the central position 
    var windowWidth = $window.innerWidth,
        center = windowWidth / 2,
        rect = e.target.getBoundingClientRect(),
        distance = center - rect.left - ((50 - 18) / 2), // Make these variables?
        positive = rect.left > center;

    // If we should animation
    if (animate) {

        var start = center,
            distance = center - rect.left - ((50 - 18) / 2), // Make these variables?
            now = Date.now(),
            duration = distance / 1;

        // Animate our elements
        service.animate(now, distance, duration, dragOptions);

    // Otherwise
    } else {

        // Update the current position
        dragOptions.currentX = distance / dragOptions.speed;

        // Move our elements
        service.updateDragElement(dragOptions);
    }
},

This now scrolls properly if I select anything left of the center. When it is right of the center it just jumps to the point instead of animating it.

Upvotes: 1

Views: 54

Answers (1)

Jacob
Jacob

Reputation: 78920

Here would be my recommended approach. First figure out the velocity you'd like:

var velocity = 1; // pixels/millisecond

...then using a requestAnimationFrame "loop":

var startPoint = center;
var endPoint = rect.left - 16;
var distance = endPoint - startPoint;
var startTime = Date.now();
var duration = distance / velocity;

animate();

function animate() {
  requestAnimationFrame(function () {
    var now = Date.now();
    var timeDelta = Math.min(now - startTime, duration);
    var timePercent = timeDelta / duration;
    var offset = timePercent * distance;
    _moveElements(dragOptions, offset, service.updateDragElement);
    if (timeDelta < duration) {
      animate();
    }
  });
}

Using setTimeout/setInterval can add artificial lag to an animation and will not perform consistently across devices.

Upvotes: 1

Related Questions