基于CropperJS与Canvas的适配裁剪工具旋转坐标异常问题
Fixing CropperJS Crop Coordinate Errors at 90/270 Degree Rotations
I see you've got 0/180 degree rotations working perfectly, but 90/270 degrees throw off your coordinates because they swap the image's width and height—something your current logic doesn't account for. Let's break down the issues and fix them step by step.
Key Problems to Address
When rotating 90 or 270 degrees:
- The image's natural width and height swap places, so your original rotation center and coordinate offset calculations don't apply
- Your crop region's width and height (
w,h) also need to swap, otherwisedrawImagewill pull the wrong portion of the image - The translation logic in your
drawRotatedfunction needs to account for the swapped dimensions to avoid shifting the image out of the canvas
Revised Implementation
Here's the updated code with fixes for 90/270 degree rotations, plus explanations of the changes:
1. Keep Your Base Rotation Function (It's Correct!)
Your existing rotate function works for the core matrix transformation—we just need to use it differently for 90/270 degrees:
function rotate(x, y, xm, ym, a) { var cos = Math.cos, sin = Math.sin, a = a * Math.PI / 180, // Convert to radians // Translate to origin, rotate, translate back xr = (x - xm) * cos(a) - (y - ym) * sin(a) + xm, yr = (x - xm) * sin(a) + (y - ym) * cos(a) + ym; return [xr, yr]; }
2. Updated Crop Function with Rotation Fixes
var cropPhoto = async (imgSrc, cropData) => { return new Promise((resolve, reject) => { var img = new Image(); var ctx, canvas; var ratio = cropData.ratio || 1; // Save original crop dimensions before any adjustments var originalCropW = cropData.width; var originalCropH = cropData.height; var x = cropData.x; var y = cropData.y; var rotateDeg = cropData.rotate; var is90Or270 = [90, -90, 270, -270].includes(rotateDeg); // Step 1: Adjust crop size to match target ratio (original logic, but keep original dimensions handy) let w = originalCropW; let h = originalCropH; if (ratio > 1) { h = ratio * originalCropH; y = y - ((h - originalCropH) / 2); } else if (ratio < 1) { w = (1 / ratio) * originalCropW; x = x - ((w - originalCropW) / 2); } img.src = imgSrc; img.onload = () => { // Step 2: Handle rotation-specific coordinate and dimension adjustments let rotatedX = x; let rotatedY = y; let rotatedW = w; let rotatedH = h; const imgCenterX = img.naturalWidth / 2; const imgCenterY = img.naturalHeight / 2; if (rotateDeg !== 0) { // Get rotated coordinates of the crop region's top-left corner const [rotatedTopLeftX, rotatedTopLeftY] = rotate(x, y, imgCenterX, imgCenterY, rotateDeg); if (is90Or270) { // Swap width/height for 90/270 rotations [rotatedW, rotatedH] = [rotatedH, rotatedW]; // Adjust coordinates to account for swapped dimensions if ([90, -270].includes(rotateDeg)) { rotatedX = rotatedTopLeftX - rotatedW; rotatedY = rotatedTopLeftY; } else if ([-90, 270].includes(rotateDeg)) { rotatedX = rotatedTopLeftX; rotatedY = rotatedTopLeftY - rotatedH; } } else { // 180 degree rotation logic (your original working code) rotatedX = rotatedTopLeftX - rotatedW; rotatedY = rotatedTopLeftY - rotatedH; } } // Step 3: Create initial canvas for cropping canvas = document.createElement("canvas"); canvas.width = rotatedW; canvas.height = rotatedH; ctx = canvas.getContext('2d'); // Draw the cropped region (adjusted for rotation) ctx.drawImage(img, rotatedX, rotatedY, rotatedW, rotatedH, 0, 0, rotatedW, rotatedH); // Step 4: Rotate the final canvas if needed if (rotateDeg !== 0) { canvas = drawRotated(rotateDeg, rotatedW, rotatedH); } // Convert to blob and resolve canvas.toBlob(blob => { try { var urlCreator = window.URL || window.webkitURL; resolve(urlCreator.createObjectURL(blob)); } catch (error) { reject(error); } }, "image/png", 1); }; // Updated rotate canvas function with dimension swap handling var drawRotated = (degrees, currentW, currentH) => { var newCanvas = document.createElement("canvas"); // Swap dimensions for 90/270 rotations if (is90Or270) { newCanvas.width = currentH; newCanvas.height = currentW; } else { newCanvas.width = currentW; newCanvas.height = currentH; } var ctx2 = newCanvas.getContext("2d"); // Adjust translation based on rotation angle switch (degrees) { case 90: case -270: ctx2.translate(currentH, 0); break; case -90: case 270: ctx2.translate(0, currentW); break; case 180: case -180: ctx2.translate(currentW, currentH); break; } ctx2.rotate(degrees * Math.PI / 180); // Draw the original cropped canvas to the new rotated canvas ctx2.drawImage(canvas, 0, 0); return newCanvas; }; }); };
What Changed?
- Track Original Crop Dimensions: We save the original
widthandheightfromcropDatabefore adjusting for the target ratio, so we don't lose reference to the original CropperJS output. - 90/270 Degree Dimension Swap: When rotating these angles, we explicitly swap the crop width and height to match the rotated image's orientation.
- Adjusted Coordinate Offsets: For 90/270 rotations, we calculate the correct coordinate offset after rotating the top-left corner, accounting for the swapped dimensions.
- Refined Canvas Rotation: The
drawRotatedfunction now uses the current crop dimensions (after ratio adjustment and rotation) to set the new canvas size and translation point.
Testing with Your Sample Data
If you pass your sample cropData with rotate: 90, the code will:
- Swap the adjusted
wandh - Calculate the rotated top-left corner of the crop region
- Adjust the
xandyto pull the correct portion of the rotated image - Create a canvas with swapped dimensions and rotate it correctly
内容的提问来源于stack exchange,提问作者Michael Artman
相关产品推荐
相关产品推荐

