首页 > 解决方案 > 画布图像顶部的可拖动文本框

问题描述

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

<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>

标签: javascripthtml5-canvas

解决方案


拖动画布内容。

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

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

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

拖动开始和拖动移动

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

  • 如果是这样并且不拖动并且在鼠标下有要拖动的文本将拖动设置为true并计算从鼠标位置到文本的偏移量xy
  • "Drag" 如果鼠标按下并拖动 true 然后通过将其设置为鼠标位置加上拖动偏移来更新选定的文本位置

拖放

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

  • 并且拖动为真,然后设置dragging为假。选定的文本项被删除
  • 设置selectedText为鼠标下的第一个文本项。

渲染循环

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

文本项

我扩展了一个数组来处理文本项。重要的功能与您textItems.getUnder(point)point.x point.y. 如果该点不在文本项上,则该函数返回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">


推荐阅读