我需要能够在画布中的图像顶部绘制正方形。我还需要能够缩放和旋转图像,同时保持绘制的正方形的纵横比相同且位于相同的位置。如果画布旋转,我仍然希望能够缩放或移动正方形,而图像不会恢复到原始位置。就目前而言,我可以缩放和旋转在画布上绘制的图像,但是当我旋转然后缩放时,它会恢复到原始位置。我还可以移动画在画布上的正方形。正方形在缩放时保持相同的纵横比。我需要帮助的问题是让图像在缩放时保持旋转,以及让正方形在旋转图像时保持在同一位置,同时仍然允许它们移动。现在当我旋转画布时,
我对 jsFiddle 不太熟悉,但我用我所有的 JS创建了一个。它不显示图像,但在我的示例应用程序中显示。我正在加载 js 文件,<canvas>
但我不知道如何在 jsFiddle 中执行此操作。如果你能帮我让它显示图像,你应该能够看到它现在是如何工作的。
提前致谢!
编辑:添加了我的 JS
var startScale = 1;
var scale = 0.8;
var $docCanvas = $('#docCanvas')[0];
var cxt = $docCanvas.getContext('2d');
var canvasWidth;
var canvasHeight;
var imageObj = new Image();
var degreesToRotate = 0;
var imageScaledWidth;
var imageScaledHeight;
var canvasState = new CanvasState($docCanvas);
function init(imageSrc) {
imageObj.onload = function () {
$docCanvas.width = canvasWidth = imageObj.width;
$docCanvas.height = canvasHeight = imageObj.height;
imageScaledHeight = imageObj.height;
imageScaledWidth = imageObj.width;
cxt.drawImage(imageObj, 0, 0);
};
canvasState.addShape(new Shape(40, 40, 150, 50)); //default is grey
imageObj.src = imageSrc;
}
$('#zoomIn').click(function () {
scale = 1.25;
resizeImage(scale);
});
$('#zoomOut').click(function () {
scale = 0.8;
resizeImage(scale);
});
$('#rotate').click(function () {
degreesToRotate += 90;
rotateImage(degreesToRotate);
});
function resizeImage(scale) {
var heightRemainder = 0;
var widthRemainder = 0;
cxt.clearRect(0, 0, $docCanvas.width, $docCanvas.height);
cxt.save();
canvasState.valid = false;
imageScaledWidth = imageScaledWidth * scale;
imageScaledHeight = imageScaledHeight * scale;
if (imageScaledWidth % 1 !== 0) {
widthRemainder = imageScaledWidth % 1;
imageScaledWidth = Math.round(imageScaledWidth);
}
if (imageScaledHeight % 1 !== 0) {
heightRemainder = imageScaledHeight % 1;
imageScaledHeight = Math.round(imageScaledHeight);
}
$docCanvas.width = imageScaledWidth;
$docCanvas.height = imageScaledHeight;
$.each(canvasState.shapes, function () {
var shape = this;
shape.resize(scale, widthRemainder, heightRemainder);
});
canvasState.draw(0, 0);
cxt.restore();
}
function rotateImage(angle) {
cxt.clearRect(0, 0, $docCanvas.width, $docCanvas.height);
cxt.save();
var centerWidth = imageScaledWidth / 2;
var centerHeight = imageScaledHeight / 2;
//move context to the center of the image
cxt.translate(centerWidth, centerHeight);
canvasState.valid = false;
//rotate around the center of the image
//the canvas rotate() method takes in an angle in radians.
cxt.rotate(angle * Math.PI / 180);
$.each(canvasState.shapes, function () {
var shape = this;
shape.rotate(angle);
});
canvasState.draw(-centerWidth, -centerHeight);
cxt.restore();
}
function CanvasState(canvas) {
// **** First some setup! ****
this.canvas = canvas;
this.width = canvas.width;
this.height = canvas.height;
this.ctx = canvas.getContext('2d');
// This complicates things a little but but fixes mouse co-ordinate problems
// when there's a border or padding. See getMouse for more detail
var stylePaddingLeft, stylePaddingTop, styleBorderLeft, styleBorderTop;
if (document.defaultView && document.defaultView.getComputedStyle) {
this.stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingLeft'], 10) || 0;
this.stylePaddingTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingTop'], 10) || 0;
this.styleBorderLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderLeftWidth'], 10) || 0;
this.styleBorderTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderTopWidth'], 10) || 0;
}
// Some pages have fixed-position bars (like the stumbleupon bar) at the top or left of the page
// They will mess up mouse coordinates and this fixes that
var html = document.body.parentNode;
this.htmlTop = html.offsetTop;
this.htmlLeft = html.offsetLeft;
// **** Keep track of state! ****
this.valid = false; // when set to false, the canvas will redraw everything
this.shapes = []; // the collection of things to be drawn
this.dragging = false; // Keep track of when we are dragging
// the current selected object. In the future we could turn this into an array for multiple selection
this.selection = null;
this.dragoffx = 0; // See mousedown and mousemove events for explanation
this.dragoffy = 0;
// **** Then events! ****
// This is an example of a closure!
// Right here "this" means the CanvasState. But we are making events on the Canvas itself,
// and when the events are fired on the canvas the variable "this" is going to mean the canvas!
// Since we still want to use this particular CanvasState in the events we have to save a reference to it.
// This is our reference!
var myState = this;
//fixes a problem where double clicking causes text to get selected on the canvas
canvas.addEventListener('selectstart', function (e) {
e.preventDefault();
return false;
}, false);
// Up, down, and move are for dragging
canvas.addEventListener('mousedown', function (e) {
var mouse = myState.getMouse(e);
var mx = mouse.x;
var my = mouse.y;
var shapes = myState.shapes;
var l = shapes.length;
for (var i = l - 1; i >= 0; i--) {
if (shapes[i].contains(mx, my)) {
var mySel = shapes[i];
// Keep track of where in the object we clicked
// so we can move it smoothly (see mousemove)
myState.dragoffx = mx - mySel.x;
myState.dragoffy = my - mySel.y;
myState.dragging = true;
myState.selection = mySel;
myState.valid = false;
return;
}
}
// havent returned means we have failed to select anything.
// If there was an object selected, we deselect it
if (myState.selection) {
myState.selection = null;
myState.valid = false; // Need to clear the old selection border
}
}, true);
canvas.addEventListener('mousemove', function (e) {
if (myState.dragging) {
var mouse = myState.getMouse(e);
// We don't want to drag the object by its top-left corner, we want to drag it
// from where we clicked. Thats why we saved the offset and use it here
myState.selection.x = mouse.x - myState.dragoffx;
myState.selection.y = mouse.y - myState.dragoffy;
myState.valid = false; // Something's dragging so we must redraw
}
}, true);
canvas.addEventListener('mouseup', function (e) {
myState.dragging = false;
}, true);
// double click for making new shapes
//canvas.addEventListener('dblclick', function (e) {
// var mouse = myState.getMouse(e);
// myState.addShape(new Shape(mouse.x - 10, mouse.y - 10, 20, 20, 'rgba(0,255,0,.6)'));
//}, true);
// **** Options! ****
this.selectionColor = '#CC0000';
this.selectionWidth = 2;
this.interval = 30;
setInterval(function () {
myState.draw(0, 0);
}, myState.interval);
}
CanvasState.prototype.addShape = function (shape) {
this.shapes.push(shape);
this.valid = false;
};
CanvasState.prototype.clear = function () {
this.ctx.clearRect(0, 0, this.width, this.height);
};
// While draw is called as often as the INTERVAL variable demands,
// It only ever does something if the canvas gets invalidated by our code
CanvasState.prototype.draw = function (centerWidth, centerHeight) {
// if our state is invalid, redraw and validate!
if (!this.valid) {
var ctx = this.ctx;
var shapes = this.shapes;
this.clear();
ctx.drawImage(imageObj, centerWidth, centerHeight, imageScaledWidth, imageScaledHeight);
// ** Add stuff you want drawn in the background all the time here **
// draw all shapes
var l = shapes.length;
for (var i = 0; i < l; i++) {
var shape = shapes[i];
// We can skip the drawing of elements that have moved off the screen:
if (shape.x > imageScaledWidth || shape.y > imageScaledHeight || shape.x + shape.w < 0 || shape.y + shape.h < 0) continue;
shapes[i].draw(ctx);
}
// draw selection
// right now this is just a stroke along the edge of the selected Shape
if (this.selection !== null) {
ctx.strokeStyle = this.selectionColor;
ctx.lineWidth = this.selectionWidth;
var mySel = this.selection;
ctx.strokeRect(mySel.x, mySel.y, mySel.w, mySel.h);
}
// ** Add stuff you want drawn on top all the time here **
this.valid = true;
}
};
// Creates an object with x and y defined, set to the mouse position relative to the state's canvas
// If you wanna be super-correct this can be tricky, we have to worry about padding and borders
CanvasState.prototype.getMouse = function (e) {
var element = this.canvas,
offsetX = 0,
offsetY = 0,
mx, my;
// Compute the total offset
if (element.offsetParent !== undefined) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
// Add padding and border style widths to offset
// Also add the <html> offsets in case there's a position:fixed bar
offsetX += this.stylePaddingLeft + this.styleBorderLeft + this.htmlLeft;
offsetY += this.stylePaddingTop + this.styleBorderTop + this.htmlTop;
mx = e.pageX - offsetX;
my = e.pageY - offsetY;
// We return a simple javascript object (a hash) with x and y defined
return {
x: mx,
y: my
};
};
// Constructor for Shape objects to hold data for all drawn objects.
// For now they will just be defined as rectangles.
function Shape(x, y, w, h, fill) {
// This is a very simple and unsafe constructor.
// All we're doing is checking if the values exist.
// "x || 0" just means "if there is a value for x, use that. Otherwise use 0."
this.x = x || 0;
this.y = y || 0;
this.w = w || 1;
this.h = h || 1;
this.fill = fill || '#000000';
}
// Draws this shape to a given context
Shape.prototype.draw = function (ctx) {
ctx.fillStyle = this.fill;
ctx.fillRect(this.x, this.y, this.w, this.h);
};
// Determine if a point is inside the shape's bounds
Shape.prototype.contains = function (mx, my) {
// All we have to do is make sure the Mouse X,Y fall in the area between
// the shape's X and (X + Height) and its Y and (Y + Height)
return (this.x <= mx) && (this.x + this.w >= mx) && (this.y <= my) && (this.y + this.h >= my);
};
Shape.prototype.resize = function (scale, widthRemainder, heightRemainder) {
if (widthRemainder > 4) {
this.x = this.x * scale + widthRemainder;
} else {
this.x = this.x * scale - widthRemainder;
}
if (heightRemainder > 4) {
this.y = this.y * scale + heightRemainder;
} else {
this.y = this.y * scale - heightRemainder;
}
this.h = this.h * scale;
this.w = this.w * scale;
};
Shape.prototype.rotate = function (angle) {
var temp;
if (angle == 90 || angle == 270) {
temp = this.w;
this.w = this.h;
this.h = temp;
}
};