我有一个 DIV 容器和其中的图像。 图像具有 CSS 转换,例如:平移、旋转、缩放
用户可以在父 div 中拖动图像(并缩放、旋转)。有时,图像的某些部分可能位于父 div 之外。
我想创建一个新图像,其大小与原始图像相同(未应用旋转,未应用缩放),但 div 中不可见的图像部分(旋转/缩放)应该最终图像中是全白的。
我尝试了各种方法,但未能得到我想要的结果。 这是我想出的代码(也在 ChatGPT 的帮助下)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Dynamic Image Transformation and Cropping</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
.container {
width: 300px; /* You can change this to any size */
height: 300px; /* You can change this to any size */
border: 2px solid #000;
position: relative;
overflow: hidden;
margin-bottom: 20px;
}
.container img {
width: 150px; /* You can change this to any size */
height: 150px; /* You can change this to any size */
/* Apply CSS transformations */
transform: translate(-30px, -30px) rotate(45deg) scale(1.2);
transform-origin: center center;
position: absolute;
top: 0;
left: 0;
}
#buttons {
margin-bottom: 20px;
}
#tempCanvases {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
#tempCanvases canvas {
border: 1px solid #ccc;
}
</style>
</head>
<body>
<h1>Dynamic Image Transformation and Cropping</h1>
<div class="container">
<img id="sourceImage" src="https://sodevrom.net/your-image.jpg" alt="Source Image">
</div>
<div id="buttons">
<button id="processButton">Process</button>
<button id="downloadButton" disabled>Download</button>
</div>
<h2>Temporary Canvases (For Debugging)</h2>
<div id="tempCanvases"></div>
<script>
// Wait for the DOM to load
document.addEventListener('DOMContentLoaded', () => {
const processButton = document.getElementById('processButton');
const downloadButton = document.getElementById('downloadButton');
const tempCanvasesDiv = document.getElementById('tempCanvases');
const sourceImage = document.getElementById('sourceImage');
let finalCanvas = null; // To store the final processed canvas
processButton.addEventListener('click', () => {
// Clear previous temporary canvases
tempCanvasesDiv.innerHTML = '';
finalCanvas = null;
downloadButton.disabled = true;
// Step 1: Get container and image dimensions
const container = sourceImage.parentElement;
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
const imageNaturalWidth = sourceImage.naturalWidth;
const imageNaturalHeight = sourceImage.naturalHeight;
const imageRenderedWidth = sourceImage.width;
const imageRenderedHeight = sourceImage.height;
console.log('Container Dimensions:', containerWidth, containerHeight);
console.log('Image Natural Dimensions:', imageNaturalWidth, imageNaturalHeight);
console.log('Image Rendered Dimensions:', imageRenderedWidth, imageRenderedHeight);
// Step 2: Get computed styles of the image
const style = window.getComputedStyle(sourceImage);
const transform = style.transform;
// If no transform is applied, set it to identity matrix
const matrix = transform === 'none' ? new DOMMatrix() : new DOMMatrix(transform);
// Extract transformation components
const scaleX = matrix.a;
const scaleY = matrix.d;
const rotateRadians = Math.atan2(matrix.b, matrix.a);
const rotateDegrees = rotateRadians * (180 / Math.PI);
const translateX = matrix.e;
const translateY = matrix.f;
console.log('Extracted Transformations:');
console.log('ScaleX:', scaleX);
console.log('ScaleY:', scaleY);
console.log('Rotate (degrees):', rotateDegrees);
console.log('TranslateX:', translateX);
console.log('TranslateY:', translateY);
// Step 3: Create the first temporary canvas (container size) with transformations applied
const tempCanvas1 = document.createElement('canvas');
tempCanvas1.width = containerWidth;
tempCanvas1.height = containerHeight;
const ctx1 = tempCanvas1.getContext('2d');
// Fill with white
ctx1.fillStyle = '#FFFFFF';
ctx1.fillRect(0, 0, tempCanvas1.width, tempCanvas1.height);
// Calculate the center of the image
const centerX = imageRenderedWidth / 2;
const centerY = imageRenderedHeight / 2;
// Apply transformations: translate, rotate, scale around the center
ctx1.translate(translateX + centerX, translateY + centerY); // Move to the center
ctx1.rotate(rotateRadians); // Apply rotation
ctx1.scale(scaleX, scaleY); // Apply scaling
ctx1.translate(-centerX, -centerY); // Move back
// Draw the image
ctx1.drawImage(sourceImage, 0, 0, imageRenderedWidth, imageRenderedHeight);
// Append the first temporary canvas for debugging
appendCanvas(tempCanvas1, 'Transformed Image');
// Step 4: Create the second temporary canvas to revert transformations and crop
const tempCanvas2 = document.createElement('canvas');
tempCanvas2.width = containerWidth;
tempCanvas2.height = containerHeight;
const ctx2 = tempCanvas2.getContext('2d');
// Fill with white
ctx2.fillStyle = '#FFFFFF';
ctx2.fillRect(0, 0, tempCanvas2.width, tempCanvas2.height);
// To revert transformations, apply inverse transformations
// Inverse scaling
const invScaleX = 1 / scaleX;
const invScaleY = 1 / scaleY;
// Inverse rotation
const invRotateRadians = -rotateRadians;
ctx2.translate(-translateX - centerX, -translateY - centerY); // Reverse translation
ctx2.translate(centerX, centerY); // Move to center
ctx2.rotate(invRotateRadians); // Apply inverse rotation
ctx2.scale(invScaleX, invScaleY); // Apply inverse scaling
ctx2.translate(-centerX, -centerY); // Move back
// Draw the image
ctx2.drawImage(sourceImage, 0, 0, imageRenderedWidth, imageRenderedHeight);
// Append the second temporary canvas for debugging
appendCanvas(tempCanvas2, 'Reverted Transformations');
// Step 5: Crop the image back to original size (natural image size)
// Create final canvas based on the image's natural size
finalCanvas = document.createElement('canvas');
finalCanvas.width = imageNaturalWidth;
finalCanvas.height = imageNaturalHeight;
const ctxFinal = finalCanvas.getContext('2d');
// Fill with white
ctxFinal.fillStyle = '#FFFFFF';
ctxFinal.fillRect(0, 0, finalCanvas.width, finalCanvas.height);
// Calculate the scaling factor between rendered and natural size
const scaleFactorX = imageNaturalWidth / imageRenderedWidth;
const scaleFactorY = imageNaturalHeight / imageRenderedHeight;
// Draw the reverted image onto the final canvas
ctxFinal.drawImage(
tempCanvas2,
0, 0, containerWidth, containerHeight, // Source rectangle
0, 0, finalCanvas.width, finalCanvas.height // Destination rectangle
);
// Append the final canvas for debugging
appendCanvas(finalCanvas, 'Final Cropped Image');
// Enable the download button
downloadButton.disabled = false;
});
downloadButton.addEventListener('click', () => {
if (!finalCanvas) return;
// Convert the final canvas to a data URL
const dataURL = finalCanvas.toDataURL('image/png');
// Create a temporary link to trigger download
const link = document.createElement('a');
link.href = dataURL;
link.download = 'processed_image.png';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
/**
* Utility function to append a canvas with a label for debugging
* @param {HTMLCanvasElement} canvas
* @param {string} label
*/
function appendCanvas(canvas, label) {
const wrapper = document.createElement('div');
const caption = document.createElement('p');
caption.textContent = label;
wrapper.appendChild(caption);
wrapper.appendChild(canvas);
tempCanvasesDiv.appendChild(wrapper);
}
});
</script>
</body>
</html>
2D 上下文
setTransform
方法接受 DOMMatrix
对象作为参数。完全使用此类对象会使事情变得更容易,因为一旦获得它,您只需应用它的 inverse()
即可返回到初始变换。
棘手的部分可能是将
transform-origin
应用于此 DOMMatrix
对象。有多种路径可以到达那里,但基本上它意味着乘以第一个转换为原点的DOMMatrix
,然后乘以实际矩阵,最后乘以最后一个矩阵以将原点重置为新的左上角。
然后您可以使用合成来使用单个画布来渲染每个步骤。
// Wait for the DOM to load
document.addEventListener('DOMContentLoaded', () => {
const processButton = document.getElementById('processButton');
const downloadButton = document.getElementById('downloadButton');
const tempCanvasesDiv = document.getElementById('tempCanvases');
const sourceImage = document.getElementById('sourceImage');
let finalCanvas = null;
processButton.addEventListener('click', () => {
// Clear previous temporary canvases
tempCanvasesDiv.innerHTML = '';
downloadButton.disabled = true;
// Step 1: Get container and image dimensions
const container = sourceImage.parentElement;
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
const imageRenderedWidth = sourceImage.width;
const imageRenderedHeight = sourceImage.height;
// Step 2: Get computed styles of the image
const style = window.getComputedStyle(sourceImage);
const transform = style.transform;
// Calculate the center of the image
const centerX = imageRenderedWidth / 2;
const centerY = imageRenderedHeight / 2;
// If no transform is applied, set it to identity matrix
const matrix = transform === 'none' ?
new DOMMatrix() :
new DOMMatrix(transform)
// Apply the transform-origin
.preMultiplySelf({ e: centerX, f: centerY })
.multiply({ e: -centerX, f: -centerY });
finalCanvas = document.createElement('canvas');
finalCanvas.width = containerWidth;
finalCanvas.height = containerHeight;
const ctx1 = finalCanvas.getContext('2d');
ctx1.setTransform(matrix);
// Draw the image transformed
ctx1.drawImage(sourceImage, 0, 0, imageRenderedWidth, imageRenderedHeight);
// Draw again, using the inverse transform
ctx1.setTransform(matrix.inverse());
// Replace the previous content by the new content
ctx1.globalCompositeOperation = "copy";
ctx1.drawImage(ctx1.canvas, 0, 0);
// Fill with white below the current drawing
ctx1.fillStyle = '#FFFFFF';
ctx1.globalCompositeOperation = "destination-over"; // Draw below
ctx1.resetTransform(); // No transform
ctx1.fillRect(0, 0, finalCanvas.width, finalCanvas.height);
// Append the canvas for debugging
appendCanvas(finalCanvas, 'Result');
// Enable the download button
downloadButton.disabled = false;
});
downloadButton.addEventListener('click', () => {
if (!finalCanvas) return;
// Convert the final canvas to a data URL
const dataURL = finalCanvas.toDataURL('image/png');
// Create a temporary link to trigger download
const link = document.createElement('a');
link.href = dataURL;
link.download = 'processed_image.png';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
/**
* Utility function to append a canvas with a label for debugging
* @param {HTMLCanvasElement} canvas
* @param {string} label
*/
function appendCanvas(canvas, label) {
const wrapper = document.createElement('div');
const caption = document.createElement('p');
caption.textContent = label;
wrapper.appendChild(caption);
wrapper.appendChild(canvas);
tempCanvasesDiv.appendChild(wrapper);
}
});
body {
font-family: Arial, sans-serif;
margin: 20px;
}
.container {
width: 300px;
/* You can change this to any size */
height: 300px;
/* You can change this to any size */
border: 2px solid #000;
position: relative;
overflow: hidden;
margin-bottom: 20px;
}
.container img {
width: 150px;
/* You can change this to any size */
height: 150px;
/* You can change this to any size */
/* Apply CSS transformations */
transform: translate(-30px, -30px) rotate(45deg) scale(1.2);
transform-origin: center center;
position: absolute;
top: 0;
left: 0;
}
#buttons {
margin-bottom: 20px;
}
#tempCanvases {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
#tempCanvases canvas {
border: 1px solid #ccc;
}
<h1>Dynamic Image Transformation and Cropping</h1>
<div class="container">
<img id="sourceImage" src="https://sodevrom.net/your-image.jpg" alt="Source Image">
</div>
<div id="buttons">
<button id="processButton">Process</button>
<button id="downloadButton" disabled>Download</button>
</div>
<h2>Temporary Canvases (For Debugging)</h2>
<div id="tempCanvases"></div>