Fabric.js - 如何从精灵表中设置动画

时间:2016-07-07 09:23:26

标签: javascript animation html5-canvas fabricjs sprite-sheet

JavaScript语法:

context.drawImage(img,srcX,srcY,srcWidth,srcHeight,x,y,width,height);

在Javascript中,如果我想为下面的spritesheet设置动画,我只需更新srcXsrcY每个动画帧,以便捕获图像的各个部分。

enter image description here

这导致每个帧被剪切并单独显示在canvas上,当以固定的帧速率更新时会产生流体精灵动画,如下所示:

enter image description here

我怎样才能使用" Fabric.js"库中?

注意:实现此目的的一种方法是设置canvasSize = frameSize,以便在任何给定时间只能看到一帧。然后通过移动图像,可以在画布内放置不同的帧以模拟动画。但是,对于大画布或可变帧大小,这不起作用。

3 个答案:

答案 0 :(得分:1)

看看这个,它做同样的事情。

一个行走的人形。 Fabric.js,图像动画。



        var URL = 'http://i.stack.imgur.com/M06El.jpg';
        var canvas = new fabric.Canvas('canvas');
        var positions = {
            topSteps:2,
            leftSteps:4
        };
        canWalk(URL,positions);
        function canWalk(URL,positions){
            var myImage = new Image();
            myImage.src = URL;
    
            myImage.onload = function() {
                var topStep = myImage.naturalHeight/positions.topSteps;
                var leftStep = myImage.naturalWidth/positions.leftSteps;
    
                var docCanvas = document.getElementById('canvas');
                docCanvas.height = topStep;
                docCanvas.width = leftStep;
                fabricImageFromURL(0,0);
                var y = 0;
                var x = 0;
                setInterval(function(){
                    if(x == positions.leftSteps)
                    {
                        x = 0;
                        y++;
                        if(y==positions.topSteps)
                        {
                            y=0;
                        }
                    }
                    fabricImageFromURL(-y*topStep,-x*leftStep);
                    x++;
                },100);
            };
        }
    
	function fabricImageFromURL(top, left)
	{
		console.log(top, left);
		fabric.Image.fromURL(URL, function (oImg) {
			oImg.set('left', left).set('top',top);
			oImg.hasControls = false;
			oImg.hasBorders  = false;
			oImg.selectable  = false;
			canvas.add(oImg);
			canvas.renderAll();
		}, {"left": 0, "top": 0, "scaleX": 1, "scaleY": 1});
	}

<canvas id="canvas"></canvas>
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.3/fabric.min.js"></script>
&#13;
&#13;
&#13;

答案 1 :(得分:0)

默认情况下,Fabric不会这样做。你需要提供来源&#39; fabric.Image object&amp; extend fabric.Image _render方法。原来看起来这个:

&#13;
&#13;
/**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Boolean} noTransform
     */
    _render: function(ctx, noTransform) {
      var x, y, imageMargins = this._findMargins(), elementToDraw;

      x = (noTransform ? this.left : -this.width / 2);
      y = (noTransform ? this.top : -this.height / 2);

      if (this.meetOrSlice === 'slice') {
        ctx.beginPath();
        ctx.rect(x, y, this.width, this.height);
        ctx.clip();
      }

      if (this.isMoving === false && this.resizeFilters.length && this._needsResize()) {
        this._lastScaleX = this.scaleX;
        this._lastScaleY = this.scaleY;
        elementToDraw = this.applyFilters(null, this.resizeFilters, this._filteredEl || this._originalElement, true);
      }
      else {
        elementToDraw = this._element;
      }
      elementToDraw && ctx.drawImage(elementToDraw,
                                     x + imageMargins.marginX,
                                     y + imageMargins.marginY,
                                     imageMargins.width,
                                     imageMargins.height
                                    );

      this._stroke(ctx);
      this._renderStroke(ctx);
    },
&#13;
&#13;
&#13;

你需要改变它:

&#13;
&#13;
fabric.util.object.extend(fabric.Image.prototype, {
    _render: function(ctx, noTransform) {
        // ...
        
        elementToDraw && ctx.drawImage(
            elementToDraw,
            this.source.x,
            this.source.y,
            this.source.width,
            this.source.height,
            x + imageMargins.marginX,
            y + imageMargins.marginY,
            imageMargins.width,
            imageMargins.height
        );
        this._renderStroke(ctx);
    }
});
&#13;
&#13;
&#13;

答案 2 :(得分:0)

您可以结合使用 clipTo -function并在循环中调用 setLeft / setTop 。在fabric.Image构造函数的选项中,您传递属性 clipTo 并告诉fabric删除图像的特定部分。然后,在循环内使用 setTop / setLeft ,触发重新绘制,从而调用clipTo,同时重新定位剪切图像,使其始终保持在同一位置。

我遇到了同样的问题,并将逻辑提取为两个函数。选项快速简化:
spriteWidth - 精灵的一个动画帧的宽度
spriteHeight - 精灵的一个动画帧的高度
totalWidth - 整个精灵图像的宽度
totalHeight - 整个精灵图像的高度
animationFrameDuration - 应显示一个精灵帧多长时间 startRandom - 如果您不想立即启动动画,而是在1秒内随机启动 - 就像fabric.Image左侧的普通左侧选项一样 顶部 - 就像fabric.Image的正常顶部选项一样

同步版本(传递HTMLImageElement):

/**
 * @param imgObj HTMLImageElement
 * @param options {
 *                  spriteWidth: number
 *                  spriteHeight: number
 *                  totalWidth: number
 *                  totalHeight: number
 *                  animationFrameDuration: number
 *                  startRandom: boolean (optional)
 *                  left: number (optional)
 *                  top: number (optional)
 *                }
 * @returns fabric.Image
 */
function animateImg(imgObj, options) {
    const left = options.left || 0;
    const top = options.top || 0;
    let x = 0;
    let y = 0;
    const image = new fabric.Image(imgObj, {
        width: options.totalWidth,
        height: options.totalHeight,
        left: left,
        top: top,
        clipTo: ctx => {
            ctx.rect(-x - options.totalWidth / 2, -y - options.totalHeight / 2, options.spriteWidth, options.spriteHeight);
        }
    });
    setTimeout(() => {
        setInterval(() => {
            x = (x - options.spriteWidth) % options.totalWidth;
            if (x === 0) {
                y = (y - options.spriteHeight) % options.totalHeight;
            }
            image.setLeft(x + left);
            image.setTop(y + top);
        }, options.animationFrameDuration)
    }, options.startRandom ? Math.random() * 1000 : 0);
    return image;
}

异步版本(传递图片网址):

/**
 * @param imgURL string
 * @param options {
 *                  spriteWidth: number
 *                  spriteHeight: number
 *                  totalWidth: number
 *                  totalHeight: number
 *                  animationFrameDuration: number
 *                  startRandom: boolean (optional)
 *                  left: number (optional)
 *                  top: number (optional)
 *                }
 * @param callback (image : fabric.Image) => void
 */
function animateImgFromURL(imgURL, options, callback) {
    const left = options.left || 0;
    const top = options.top || 0;
    let x = 0;
    let y = 0;
    fabric.Image.fromURL(
        imgURL,
        image => {
            setTimeout(() => {
                setInterval(() => {
                    x = (x - options.spriteWidth) % options.totalWidth;
                    if (x === 0) {
                        y = (y - options.spriteHeight) % options.totalHeight;
                    }
                    image.setLeft(x);
                    image.setTop(y);
                }, options.animationFrameDuration)
            }, options.startRandom ? Math.random() * 1000 : 0);
            callback(image);
        }, {
            width: options.totalWidth,
            height: options.totalHeight,
            left: 0,
            top: 0,
            left: left,
            top: top,
            clipTo: ctx => {
                ctx.rect(-x - options.totalWidth / 2, -y - options.totalHeight / 2, options.spriteWidth, options.spriteHeight);
            }
        });

请注意,上述功能不会重新渲染画布,您必须自己这样做 您可以使用上面这样的代码来并排两次sprite动画(一次是同步版本,一次是异步的):

// Assuming:
// 1. canvas was created
// 2. Sprite is in html with id 'walking'
// 3. Sprite is within folder 'images/walking.jpg'
const img1 = animateImg(document.getElementById('walking'), {
    spriteWidth: 125,
    spriteHeight: 125,
    totalWidth: 500,
    totalHeight: 250,
    startRandom: true,
    animationFrameDuration: 150,
    left: 125,
    top: 0
});
canvas.add(img1);
animateImgFromURL('images/walking.jpg', {
    spriteWidth: 125,
    spriteHeight: 125,
    totalWidth: 500,
    totalHeight: 250,
    startRandom: true,
    animationFrameDuration: 150
}, image => canvas.add(image));
// hacky way of invoking renderAll in a loop:
setInterval(() => canvas.renderAll(), 10);