I'd like to know how to call the animate function through requestAnimationFrame only when it's realy needed. Currently the animate is called all the time what generates an overhead I guess.

I already tried inside my animate function to compare targetRadius and the inital radius and return false once they are the same. Unfortunately this doesn't work at all.

Can someone explain me how to solve that?

jsfiddle

HTML:

  <canvas id="ddayCanvas" width="288" height="288" data-image="http://www.topdesignmag.com/wp-content/uploads/2011/07/64.png">
    <div>
        <div class="product-image"></div>
        <div class="product-box">...</div>
        <a href="#" class="overlay">...</a>
    </div>    
  </canvas>

JS:

// Options
var maxImageWidth = 250,
    maxImageHeight = 196;

var canvas = $('#ddayCanvas'),
    canvasWidth = canvas.width(),
    canvasHeight = canvas.height(),
    sectorColor = $('.product-box').css('background-color'),
    context = canvas[0].getContext('2d'),
    imageSrc = canvas.data('image'),
    imageObj = new Image(),
    imageWidth, imageHeight,
    mouseover = false;

    imageObj.onload = function() {
        imageWidth = this.width;
        imageHeight = this.height;

        if (imageWidth > maxImageWidth){
            imageHeight = imageHeight - (imageWidth - maxImageWidth);
            imageWidth = maxImageWidth;
        }

        if (imageHeight > maxImageHeight) {
            imageWidth = imageWidth - (imageHeight - maxImageHeight);
            imageHeight = maxImageHeight;
        }

        drawDday(90); 
    };

    imageObj.src = imageSrc;  

function drawDday (radius) {
    context.clearRect(0, 0, canvasWidth, canvasHeight);
    context.drawImage(imageObj, Math.ceil((canvasWidth - imageWidth) / 2), Math.ceil((canvasHeight - imageHeight) / 2), imageWidth, imageHeight);
    context.fillStyle = sectorColor;
    context.beginPath();
    context.rect(0, 0, canvasWidth, canvasHeight);
    context.arc(canvasWidth/2, canvasHeight/2, radius, 0, Math.PI*2, true);
    context.closePath();
    context.fill();

    // Check out the console
    console.log('test');
}


var radius = baseRadius = 90,
    targetRadius = 110,
    ease = 50,
    speed = 2;

function animate(){
    if(mouseover){
        radius += ((targetRadius-radius)/ease)*speed;
    } else {
        radius -= ((radius-baseRadius)/ease)*speed;
    }
    if(radius > targetRadius) radius = targetRadius;
    if(radius < baseRadius) radius = baseRadius;

    drawDday(radius);   
    requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

canvas.on('mouseover', function(e){
    mouseover = true;
}).on('mouseout', function(){
    mouseover = false;
});
有帮助吗?

解决方案

You need to implement a condition so you can break the loop, for example (adopt as needed):

var isRunning = true;

function loop() {

    ... funky stuff here ...

    /// test condition before looping
    if (isRunning) requestAnimationFrame(loop);
}

Now when you set isRunning to false the loop will break. For convenience it's recommended that you have a method to start and stop the loop:

function startLoop(state) {

    if (state && !isRunning) {
        isRunning = true;
        loop();             /// starts loop

    } else if (!state && isRunning) {
        isRunning = false;
    }
}

The condition can be set by anything you need it to be set by, for example on a callback after an animation has finished etc. The important part is that the condition flag is available to both scopes using it (ie. most commonly in the global scope).

UPDATE:

More specific in this case is that your condition (radius) will never reach the condition required to eventually stop the loop.

Here is what you can do to fix this:

DEMO

var isPlaying = false;

function animate(){
    /**
     * To make sure you will reach the condition required you need
     * to either make sure you have a fall-out for the steps or the
     * step will become 0 not adding/subtracting anything so your
     * checks below won't trigger. Here we can use a simple max of
     * the step and a static value to make sure the value is always > 0
    */
    if(mouseover){
        radius += Math.max( ((targetRadius-radius)/ease)*speed, 0.5);
    } else {
        radius -= Math.max( ((radius-baseRadius)/ease)*speed,   0.5);
    }

    /**
     * Now the checks will trigger properly and we can use the
     * isPlaying flag to stop the loop when targets are reached.
    */
    if(radius >= targetRadius) {
        radius = targetRadius;
        isPlaying = false;              /// stop loop after this
    } else if (radius <= baseRadius) {
        radius = baseRadius;
        isPlaying = false;              /// stop loop after this
    }
    
    drawDday(radius);

    /// loop?
    if (isPlaying === true) requestAnimationFrame(animate);
}

In order to trigger the loop we use a method that will check if the loop is running, if not it will reset the isPlaying flag and start the loop. We do this inside both mouseover and mouseout:

canvas.on('mouseover', function(e){
    mouseover = true;
    startAnim();

}).on('mouseout', function(){
    mouseover = false;
    startAnim();
});

The method is simply checking isPlaying and if not set it set it to true and starts the loop - this so that the loop is only started once:

function startAnim() {
    if (!isPlaying) {
        isPlaying = true;
        requestAnimationFrame(animate);
    }
}

In the demo I added console logging to show when the loop is running and when targets are hit.

Hope this helps.

其他提示

The reason your animate function is being called continuously is because you start off by calling requestAnimationFrame(animate); and then each call to animate unconditionally calls requestAnimationFrame(animate); again. The cycle is never going to be broken unless you use cancelAnimationFrame at some point (which you don't), or make sure that animate only requests another frame if it's needed.

Another issue is the fact that radius will currently never reach either targetRadius nor baseRadius, and therefore neither of the following will ever be true:

if(radius > targetRadius) radius = targetRadius;
if(radius < baseRadius) radius = baseRadius;

This isn't directly responsible for the continual calls to animate, but since targetRadius and baseRadius are being used to indicate the end-points of your animation then we need to form some sort of sensible conditional with them.

So, you could do something like: http://jsfiddle.net/PLDUq/9/

var radius = baseRadius = 50,
    targetRadius = 110,
    ease = 50,
    speed = 12,
    currentAnim;

function animate(){
    if(mouseover){
        radius += ((targetRadius-radius)/ease)*speed;
    } else {
        radius -= ((radius-baseRadius)/ease)*speed;
    }

    drawDday(radius);

    if(Math.round(radius) >= targetRadius) {
        // uses Math.round() to ensure condition can be fulfilled

        radius = targetRadius;
        return; // doesn't call next frame
    }
    if(Math.round(radius) <= baseRadius) {
        radius = baseRadius;
        return; // doesn't call next frame
    }

    requestAnimationFrame(animate);
}

canvas.on('mouseenter mouseleave', function (e) {
    if (currentAnim) {requestAnimationFrame(currentAnim);}
    // cancels current animation if one is playing to
    // prevent several concurrent loops calling animate()

    mouseover = (e.type === 'mouseenter');

    requestAnimationFrame(animate);
});
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top