画布图像顶部的可拖动文本框



大家好,所以我正在尝试在画布上制作一个可拖动的文本框。但是当我似乎无法弄清楚功能时。我想做的只是将文本框拖到图像顶部。能够单击并拖动文本真的很酷。有人知道如何做到这一点吗?它变得令人头疼。如果您有任何建议,那就太好了。

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<canvas id="canvas" width='500' height='500' ref='canvas' @mousedown='handleMouseDown' @mouseout='resetSelectedText' @mouseup='resetSelectedText' @mousemove='handleMouseMove'></canvas>
<input v-model="text" placeholder='type your text'>
<button @click='addText'>
add text
</button>
<div v-for="(text, index) in texts">
{{text.text}} <div @click='removeText(index)'>X</div>
</div>
<img src ='https://shop-resources.prod.cms.tractorsupply.com/resource/image/18248/portrait_ratio3x4/595/793/4c37b7f6d6f9d8a5b223334f1390191b/JJ/ten-reasons-not-to-buy-an-easter-bunny-main.jpg' @click="changeBackground('http://upload.wikimedia.org/wikipedia/commons/7/79/Big_Buck_Bunny_small.ogv')">
<img src ='https://ce.prismview.com/api/files/templates/43s327k3oqfsf7/poster/poster.jpg' @click="changeBackground('http://ce.prismview.com/api/files/templates/43s327k3oqfsf7/main/360/43s327k3oqfsf7_360.mp4')">
<video id="video" ref='video' :src="source" controls="false" autoplay loop></video>
</div>
<script>
new Vue({
el: '#app',
data: {
source: "http://upload.wikimedia.org/wikipedia/commons/7/79/Big_Buck_Bunny_small.ogv",
canvas: null,
ctx: null,
video: null,
text:'',
timer: null,
texts: [],
selectedText: null,
startX: null,
startY: null
},
methods: {
addText(){
if(this.text.length){
let textObj = {
text: this.text,
x: 20,
y: this.texts.length * 20 + 20
};
this.texts.push(textObj);
this.text = '';
}
},
removeText(i){
this.texts.splice(i);
},
textInRange(x, y, textIndex){
let t = this.texts[textIndex];
return (x >= t.x && x <= t.x + t.width && y >= t.y - t.height && y <= t.y);
},
handleMouseDown(e){
console.log('yes', e.offsetX);
this.startX = parseInt(e.clientX - 20);
this.startY = parseInt(e.clientY - 20);
for(let i =0; i<this.texts.length; i++){
if(this.textInRange(this.startX, this.startY, i)){
this.selectedText = i;
console.log(this.selectedText);
} else { console.log('not in range');}
}
},
resetSelectedText(e){
this.selectedText = -1;
},
handleMouseMove(e){
if(this.selectedText <0){
return;
}
let mouseX = parseInt(e.clientX - e.offsetX);
let mouseY = parseInt(e.clientY - e.offsetY);
let dx = mouseX - this.startX;
let dy = mouseY - this.startY;
if(this.selectedText!=null){
let t = this.texts[this.selectedText];
t.x += dx;
t.y = dy;
}
},
drawFrame (){
console.log("drawing");
this.ctx.drawImage(this.video, 0, 0,);
this.ctx.fillStyle = 'red';
this.ctx.font = "30px Arial";
for(let i =0; i<this.texts.length; i++){
this.ctx.fillText(this.texts[i].text, this.texts[i].x, this.texts[i].y);
}
this.timer = setTimeout(() => {
this.drawFrame()
}, 1000/30);
},
initCanvas(){
this.canvas = this.$refs['canvas'];
this.video = this.$refs['video'];
this.ctx = this.canvas.getContext('2d');
const vm = this;
this.video.addEventListener('play', function(){
vm.video.style.display = 'none';
vm.drawFrame();
})
},
changeBackground(source){
if(source!=this.video.src){
clearTimeout(this.timer);
this.source = source;
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.restore();
}
}
},
mounted: function(){
this.initCanvas();
}
});
</script>

拖动画布内容。

使用mousedownmouseupmousemove事件侦听器来更新鼠标状态。例如位置和按钮状态。

拖动渲染项的逻辑应在主渲染循环中完成。在示例中renderLoop在呈现任何内容之前调用handleMouse

我没有做一个完整的VUE应用程序示例,而是做了最基本的文本拖放,所以你可以看到一个代码示例。

拖动

开始和拖动移动

handleMouse检查鼠标是否关闭mouse.button === true

  • 如果是这样并且不拖动,并且鼠标集下有文本要拖动拖动到 true 并计算从鼠标 POS 到文本x的偏移量,y
  • "拖动"如果鼠标
  • 向下并拖动 true,则通过将其设置为鼠标位置加上拖动偏移来更新所选文本位置

拖放

如果鼠标向上mouse.button === false

  • 拖动为真,然后将dragging设置为 false。删除所选文本项
  • selectedText设置为鼠标下的第一个文本项。

渲染循环

还处理鼠标和文本的突出显示和光标,以向用户提供积极的反馈。

文本项

我扩展了一个数组来处理文本项。重要函数与您在point.xpoint.y下返回文本项textItems.getUnder(point)非常相似。如果该点不在文本项上,则该函数返回undefined

例如,它可能无法满足您的所有需求,并且绝不是处理渲染画布内容的拖放的唯一方法。

我希望这有所帮助。

requestAnimationFrame(renderLoop);          
const ctx = canvas.getContext("2d");
var selectedText;
const mouse = {
x: 0, 
y: 0,
bounds: canvas.getBoundingClientRect(),
button: false,
dragging: false,
dragOffsetX: 0,
dragOffsetY: 0,
events(event) {  // mouse event handler should only record current mouse state
const m = mouse;
if (event.type === "mousedown") { m.button = true }
else if (event.type === "mouseup") { m.button = false }
m.x = event.pageX - m.bounds.left - scrollX;
m.y = event.pageY - m.bounds.top - scrollY;
}   
};
document.addEventListener("mousemove", mouse.events);
document.addEventListener("mousedown", mouse.events);
document.addEventListener("mouseup", mouse.events);
function renderLoop(time) {
if (!textItems.length) { addDemoText() }
textItems[0].update("Frame time: " + time.toFixed(3) + "ms");
var cursor = "default";
handleMouse();
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
textItems.draw(ctx);
if (selectedText) { 
cursor = mouse.dragging ? "none" : "move";
ctx.fillStyle = "#08F"; // highlight selected text
selectedText.draw();
}
canvas.style.cursor = cursor;
requestAnimationFrame(renderLoop); 
}
function handleMouse() {
const m = mouse;
const text = selectedText;
if (m.button) {
if (!m.dragging && text !== undefined) {
m.dragging = true;
m.dragOffsetX = text.x - m.x;
m.dragOffsetY = text.y - m.y;
}
if (m.dragging) {
text.x = m.x + m.dragOffsetX;
text.y = m.y + m.dragOffsetY;
text.keepOnCanvas()
}
} else {
if (m.dragging) {                 
selectedText = undefined;
m.dragging = false;
}
selectedText = textItems.getUnder(m);
}
}
const textItems = Object.assign([],{
getUnder(point) { // returns undefined if no text under
for(const t of this) {
if (point.x >= t.x && point.x <= t.x + t.width && point.y < t.y + t.size && point.y >= t.y) {
return t;
}
}
},
add(ctx, text, x,  y, color = "#000", size = 24, font = "arial") { // context ctx to measure the text
var item;
ctx.font = size + "px " + font;
const width = ctx.measureText(text).width;
this.push(item = {text, x, y, color, font, size, width,
draw() { 
ctx.font = this.size + "px " + this.font;
ctx.textBaseline = "hanging";
ctx.fillText(this.text, this.x, this.y);
},
keepOnCanvas() {
const maxX = ctx.canvas.width - this.width;
const maxY = ctx.canvas.height - this.size;
this.x < 0 && (this.x = 0);
this.y < 0 && (this.y = 0);
this.x >= maxX && (this.x = maxX - 1);
this.y >= maxY && (this.y = maxY - 1);
}, 
update(text) {
this.text = text;
ctx.font = this.size + "px " + this.font;
this.width = ctx.measureText(text).width;
this.keepOnCanvas();
}
});
return item;
},
draw(ctx) {
for(const text of this) {
ctx.fillStyle = text.color;
text.draw();
}
}
});
function addDemoText() { 
var idx = 0;
textItems.add(ctx, "", 0, 0);
for (const t of "HI there! Some text to move with the mouse. Move mouse 🐭 over text items. Click and drag to move the text. 😁 😀".split(" ")) {
const text = textItems.add(ctx, t, idx % (canvas.width - 80), (idx / (canvas.width - 80) | 0) * 26 + 26);
text.keepOnCanvas();
idx += text.width + 12
}
}
canvas {
border: 1px solid black;
};
<canvas id="canvas" width="600" height="180">

最新更新