JavaScript语法:
context.drawImage(img,srcX,srcY,srcWidth,srcHeight,x,y,width,height);
在Javascript中,如果我想为下面的spritesheet设置动画,我只需更新srcX
和srcY
每个动画帧,以便捕获图像的各个部分。
这导致每个帧被剪切并单独显示在canvas
上,当以固定的帧速率更新时会产生流体精灵动画,如下所示:
我怎样才能使用" Fabric.js"库中?
注意:实现此目的的一种方法是设置canvasSize = frameSize
,以便在任何给定时间只能看到一帧。然后通过移动图像,可以在画布内放置不同的帧以模拟动画。但是,对于大画布或可变帧大小,这不起作用。
答案 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;
答案 1 :(得分:0)
默认情况下,Fabric不会这样做。你需要提供来源&#39; fabric.Image object&amp; extend fabric.Image _render方法。原来看起来这个:
/**
* @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;
你需要改变它:
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;
答案 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);