In a Vue.js component, I’m loading an image and then adding text with specified size, color, and font-family. After adding text, the user can drag it anywhere on the image and then download the modified image. Everything works perfectly, but when I try to create a canvas, the Y position of my text changes every time.
<template>
<div class="col-12">
<div ref="imageContainer" id="imageContainer" class="p-0 m-0">
<img
:src="photo.file_path"
alt=""
class="img-fluid"
draggable="false"
id="image"
ref="image"
/>
<p
v-for="(text, index) in texts"
:key="index"
class="text-box mb-0"
:style="{
top: text.top + 'px',
left: text.left + 'px',
color: text.color,
fontSize: text.size + 'px',
fontFamily: text.font,
}"
@mousedown="startDrag(index)"
>
{{ text.content }}
</p>
</div>
<div class="col-12 mt-4">
<div class="row">
<div class="col-md-4 form-group mb-2">
<label for="textInput">Text:</label>
<input
type="text"
v-model="textInput"
id="textInput"
class="form-control"
/>
</div>
<div class="col-md-4 form-group mb-2">
<label for="textSize">Text Size:</label>
<input
type="number"
v-model="textSize"
id="textSize"
class="form-control"
/>
</div>
<div class="col-md-4 form-group mb-2">
<label for="textColor">Text Color:</label>
<input
type="color"
v-model="textColor"
id="textColor"
class="form-control"
/>
</div>
<div class="col-md-4 form-group mb-2">
<label for="textFont">Font family:</label>
<select
v-model="textFont"
id="textFont"
class="form-control form-select"
>
<option v-for="font in fonts" :key="font" :value="font">
{{ font }}
</option>
</select>
</div>
</div>
<div class="text-end">
<button class="btn btn-primary" @click="addText">
Add Text
</button>
<button class="btn btn-success mx-1" @click="downloadImage">
Download Photo
</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: "EditPhoto",
props: ["photo"],
data() {
return {
texts: [],
fonts: ["sans-serif", "Arial", "Times New Roman", "Courier New"],
dragging: null,
textInput: "",
textColor: "#000000", // Default text color
textSize: 100, // Default text size
textFont: "sans-serif", // Default font family
};
},
mounted() {
window.addEventListener("mousemove", this.handleMouseMove);
window.addEventListener("mouseup", this.handleMouseUp);
this.setContainer();
},
methods: {
setContainer() {
const imageContainer = this.$refs.imageContainer;
const image = this.$refs.image;
image.onload = () => {
// imageContainer.style.width = image.naturalWidth + 'px';
// imageContainer.style.height = image.naturalHeight + 'px';
imageContainer.style.overflow = 'hidden';
imageContainer.style.position = 'relative';
};
},
startDrag(index) {
this.dragging = index;
},
handleMouseMove(event) {
if (this.dragging !== null) {
this.texts[this.dragging].left = event.pageX;
this.texts[this.dragging].top = event.pageY;
}
},
handleMouseUp() {
this.dragging = null;
},
addText() {
if (this.textInput == "") {
toastr.success("Write something to add text.");
return;
}
this.texts.push({
content: this.textInput,
color: this.textColor,
size: parseInt(this.textSize),
font: this.textFont,
top: 100, // Initial position
left: 100, // Initial position
});
this.textInput = "";
toastr.success("Text added successfully.");
},
downloadImage() {
const img = this.$refs.image;
// Wait for the image to load before performing operations
// img.onload = () => {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
// Set canvas dimensions to match the displayed image dimensions
canvas.width = img.width;
canvas.height = img.height;
// Draw the image
context.drawImage(img, 0, 0, img.width, img.height);
// Add a border to the canvas
const canvasBorderWidth = 5; // Set the width of the border
const canvasBorderColor = 'red'; // Set the color of the border
context.lineWidth = canvasBorderWidth;
context.strokeStyle = canvasBorderColor;
context.strokeRect(0, 0, canvas.width, canvas.height);
// Replace texts on their positions
this.texts.forEach((text) => {
context.fillStyle = text.color;
context.font = `${text.size}px ${text.font}`;
const scaledLeft = text.left;
const scaledTop = text.top;
context.fillText(text.content, scaledLeft, scaledTop);
console.log('position:', scaledLeft, scaledTop);
console.log(canvas);
});
// Display the canvas in the image container
const imageContainer = this.$refs.imageContainer;
imageContainer.innerHTML = ''; // Clear existing content
imageContainer.appendChild(canvas);
// Use a try-catch block to handle errors when creating the download link
try {
const downloadLink = document.createElement("a");
downloadLink.href = canvas.toDataURL();
downloadLink.download = "edited_image.png";
// Append the link to the document and trigger a click
document.body.appendChild(downloadLink);
downloadLink.click();
// Remove the link from the document
document.body.removeChild(downloadLink);
} catch (error) {
console.error("Error creating or triggering download link:", error);
}
// };
}
},
};
</script>
<style scoped>
.text-box {
position: absolute;
cursor: pointer;
}
img {
user-select: none;
}
</style>
I want my users to be able to upload an image, add text to it, freely drag the added text anywhere on the image, and then download the modified image.