Анимационная область отсечения в элементе <canvas> - PullRequest
3 голосов
/ 02 января 2012

Я использую stackoverflow.com в качестве источника вдохновения и решения проблем уже много месяцев.Я просто никогда не сталкивался с проблемой без решения до сих пор, поэтому я сначала хотел бы представиться и поделиться своей проблемой со всеми, кто интересуется.

Для прошлой парынедель я пытался анимировать определенные формы и линии на элементе canvas, чтобы создать некоторые интересные эффекты - например, почерк или аналогичные.
Чтобы добиться этого, я использую некоторую технику, которая использует .clip ()команда элемента canvas, чтобы скрыть и постепенно выявить области, под которыми «ждет» предварительно визуализированное изображение (формы, линии ...).Проблема, с которой я здесь сталкиваюсь, связана с переменными, которые определяют область отсечения в элементе canvas.Кажется, есть какая-то странная проблема с увеличением (но не уменьшением) значений в анимации.
И поскольку все это звучит очень странно, о чем я знаю, вот соответствующая часть кода, о которой я говорю.

$(document).ready(function() {
    var ctx = $( "#canvas" )[0].getContext("2d");
    ctx.fillStyle = "#a00";
    var recW = 200;

    function animate() {
        ctx.clearRect(0,0,400,400);

        ctx.beginPath();
        ctx.rect(50,50,recW,100);
        ctx.clip();

        ctx.beginPath();
        ctx.arc(250,100,90,0,Math.PI*2,true);
        ctx.fill();

        recW--;

        if (recW == 150) clearInterval(run);
    }
    var run = setInterval(function() { animate(); },60);
});

Приведенный выше код работает отлично.Он рисует прямоугольник на холсте 400 * 400, использует его в качестве области отсечения, затем рисует круг, и затем этот круг соответственно обрезается.Через интервал анимации длина прямоугольника отсечения затем уменьшается до тестового значения 150. Пока все хорошо.Но вот часть, которая заставляла меня загадывать часами подряд:

$(document).ready(function() {
    var ctx = $( "#canvas" )[0].getContext("2d");
    ctx.fillStyle = "#a00";
    var recW = 150;

    function animate() {
        ctx.clearRect(0,0,400,400);

        ctx.beginPath();
        ctx.rect(50,50,recW,100);
        ctx.clip();

        ctx.beginPath();
        ctx.arc(250,100,90,0,Math.PI*2,true);
        ctx.fill();

        recW++;

        if (recW == 200) clearInterval(run);
    }
    var run = setInterval(function() { animate(); },60);
});

Если я переверну всю анимацию, начнем с ширины 150 для прямоугольника отсечения и увеличим ее с recW ++ дотест-значение 200, внезапно анимация больше не работает.Постепенное увеличение переменной работает без проблем, но видимая область отсечения не увеличивается.

Я подозреваю, что, возможно, я просто упускаю из виду очевидное здесь, но я просто не могу найти ошибку, и я быбудь очень благодарен, если кто-нибудь может указать мне правильное направление;)

Большое спасибо
Трикон

Ответы [ 2 ]

1 голос
/ 30 марта 2012

Вот хорошее руководство о том, как анимировать обтравочный контур. Ваша проблема в том, что после того, как клип сделан, его нужно убрать, чтобы увеличить, поэтому вы используете состояния сохранения исалфетки, чтобы сделать этот эффект.

Вот пример кода.

<canvas  id="slide29Canvas2" width="970px" height="600px"></canvas>

<script>
    // Grabs the canvas element we made above
var ca1=document.getElementById("slide29Canvas1");

// Defines the 2d thing, standard for making a canvas
var c1=ca1.getContext("2d");

// Creates an image variable to hold and preload our image (can't do animations on an image unless its fully loaded)
var img1 = document.createElement('IMG');

// Loads image link into the img element we created above
img1.src = "http://tajvirani.com/wp-content/uploads/2012/03/slide29-bg_1.png";

// Creates the first save event, this gives us a base to clear our clipping / mask to since you can't just delete elements.
c1.save();

// Our function for when the image loads
img1.onload = function () {

    // First call to our canvas drawing function, the thing that is going to do all the work for us.
        // You can just call the function but I did it through a timer
    setTimeout(function() { drawc1r(0); },5);

        // The function that is doing all the heavy lifting. The reason we are doing a function is because
        // to make an animation we have to draw the circle (or element) frame by frame, to do this manually would be to time
        // intensive so we are just going to create a loop to do it. 'i' stands for the radius of our border
        // so over time our radius is going to get bigger and bigger.
    function drawc1r(i) {

        // Creates a save state. Imagine a save state like an array, when you clear one it pops last element in the array off the stack
        // When you save, it creates an element at the top of the stack. So if we cleared without making new ones, we would end up with nothing on our stage.
    c1.save();

        // This clears everything off the stage, I do this because our image has transparency, and restore() (the thing that pops one off the stack)
        // Doesn't clear off images, and so if we stick an image on the stage over and over, the transparency will stack on top of each other and
        // That isn't quite what we want.
    c1.clearRect(0, 0, ca1.width, ca1.height);

        // Adds one to the radius making the circle a little bigger with every step
    i++;

        // Tells canvas we are going to start creating an item on the stage - it can be a line, a rectangle or any shape you draw, but whatever
        // after this path will be added to the clip when its called. I can have 3 rectangles drawn and that would make a clip.
    c1.beginPath();

        // Can't make a true circle, so we make an arced line that happens to trace a circle - 'i' is used to define our radius.
    c1.arc(853, 320, i, 0, 2 * Math.PI, false);

        // After everything is defined, we make a clip area out of it.
    c1.clip();

        // Now that we have the clip added to it, we are going to add the image to the clip area.
    c1.drawImage(img1, 0, 0);

        // This pops one off the stack which gets rid of the clip so we can enlarge it and make it again on the next pass
    c1.restore();

        // Here is the final size of the circle, I want it to grow the circle until it hits 800 so we set a timeout to run this function again
        // until we get the size we want. The time in milliseconds pretty much defines your framerate for growing the circle. There are other
        // methods for doing this that give you better frame rates, but I didn't have much luck with them so I'm not going to include them.
    if(i < 800) {
        setTimeout(function() { drawc1r(i); },5);
    }

}

1 голос
/ 03 января 2012

Ваша проблема сложна для отладки, если у вас нет большого опыта (чего у меня тоже нет).

Причина, по которой вы можете анимировать уменьшающуюся форму и не увеличивающую ее, заключается в том, чтоЯ подозреваю, что вы делаете клипсы вместе.Поэтому, когда они становятся меньше, все выглядит хорошо, так как вы ожидаете наименьшую площадь клипа.Однако когда клип становится больше, вы выполняете AND с исходной небольшой областью клипа, поэтому создается впечатление, что анимации нет.

Чтобы исправить это, необходимо выполнить вызов restore() в концетвой клипОднако, чтобы это работало, вам также нужен вызов save() в начале вашего клипа.Наконец, я добавил ограничивающий прямоугольник, чтобы указать, где именно находится клип, и, поскольку это заливка и обводка, я поместил еще один оператор beginPath, чтобы не обводить круг за пределами области клипа (которую мы только что восстановили).

Вот полный jsFiddle код

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

ctx.fillStyle = "#a00";
var recW = 150;

function animate2() {
    ctx.clearRect(50,50,canvas.width,recW - 1);

    ctx.save();

    ctx.beginPath();
    ctx.rect(50, 50, recW, recW);
    ctx.clip();

    ctx.beginPath();
    ctx.arc(250,100,90,0,Math.PI*2,true);
    ctx.fill();

    ctx.restore();
    ctx.beginPath();
    ctx.rect(50 - 1, 50 - 1, recW + 2, recW + 2);
    ctx.lineWidth = 10;
    ctx.stroke();
    console.log(recW);
    recW++;

    if (recW == 300) clearInterval(run);
}
var run = setInterval(function() { animate2(); },5);
...