javascript - 带有手绘线条的 HTML5 Canvas

标签 javascript html canvas html5-canvas

关闭。这个问题需要details or clarity .它目前不接受答案。












想改进这个问题?通过 editing this post 添加详细信息并澄清问题.

4年前关闭。




Improve this question




我是 HTML5 中 Canvas 的新学习者,目前正在尝试为 children 制作一个简单的画线项目。我将背景图像插入 Canvas 中,并尝试提出一种手绘线绘制方法,但没有结果。有人可以帮我修复下面的代码吗?之前谢谢大家。

<!DOCTYPE HTML>
<html>
    <script type="text/javascript">
    var canvas, ctx, flag = false,
        prevX = 0,
        currX = 0,
        prevY = 0,
        currY = 0,
        dot_flag = false;

    var x = "black",
        y = 2;

    function init() {
        canvas = document.getElementById('can');
        ctx = canvas.getContext("2d");
        w = canvas.width;
        h = canvas.height;

        canvas.addEventListener("mousemove", function (e) {
            findxy('move', e)
        }, false);
        canvas.addEventListener("mousedown", function (e) {
            findxy('down', e)
        }, false);
        canvas.addEventListener("mouseup", function (e) {
            findxy('up', e)
        }, false);
        canvas.addEventListener("mouseout", function (e) {
            findxy('out', e)
        }, false);
    }

    function color(obj) {
        switch (obj.id) {
            case "green":
                x = "green";
                break;
            case "blue":
                x = "blue";
                break;
            case "red":
                x = "red";
                break;
            case "yellow":
                x = "yellow";
                break;
            case "orange":
                x = "orange";
                break;
            case "black":
                x = "black";
                break;
            case "white":
                x = "white";
                break;
        }
        if (x == "white") y = 14;
        else y = 2;

    }

    function draw() {
        ctx.beginPath();
        ctx.moveTo(prevX, prevY);
        ctx.lineTo(currX, currY);
        ctx.strokeStyle = x;
        ctx.lineWidth = y;
        ctx.stroke();
        ctx.closePath();
    }

    function erase() {
        var m = confirm("Want to clear");
        if (m) {
            ctx.clearRect(0, 0, w, h);
            document.getElementById("canvasimg").style.display = "none";
        }
    }

    function save() {
        document.getElementById("canvasimg").style.border = "2px solid";
        var dataURL = canvas.toDataURL();
        document.getElementById("canvasimg").src = dataURL;
        document.getElementById("canvasimg").style.display = "inline";
    }

    function findxy(res, e) {
        if (res == 'down') {
            prevX = currX;
            prevY = currY;
            currX = e.clientX - canvas.offsetLeft;
            currY = e.clientY - canvas.offsetTop;

            flag = true;
            dot_flag = true;
            if (dot_flag) {
                ctx.beginPath();
                ctx.fillStyle = x;
                ctx.fillRect(currX, currY, 2, 2);
                ctx.closePath();
                dot_flag = false;
            }
        }
        if (res == 'up' || res == "out") {
            flag = false;
        }
        if (res == 'move') {
            if (flag) {
                prevX = currX;
                prevY = currY;
                currX = e.clientX - canvas.offsetLeft;
                currY = e.clientY - canvas.offsetTop;
                draw();
            }
        }
    }
    </script>
    <body onload="init()" style="background-image: src=c:/WebProgram/Pictures/test1.png;">
        <canvas id="can" width="520" height="700" style="position:absolute;top:10%;left:10%;border:2px solid;"></canvas>
        <div style="position:absolute;top:12%;left:43%;">Choose Color</div>
        <div style="position:absolute;top:15%;left:45%;width:10px;height:10px;background:green;" id="green" onclick="color(this)"></div>
        <div style="position:absolute;top:15%;left:46%;width:10px;height:10px;background:blue;" id="blue" onclick="color(this)"></div>
        <div style="position:absolute;top:15%;left:47%;width:10px;height:10px;background:red;" id="red" onclick="color(this)"></div>
        <div style="position:absolute;top:17%;left:45%;width:10px;height:10px;background:yellow;" id="yellow" onclick="color(this)"></div>
        <div style="position:absolute;top:17%;left:46%;width:10px;height:10px;background:orange;" id="orange" onclick="color(this)"></div>
        <div style="position:absolute;top:17%;left:47%;width:10px;height:10px;background:black;" id="black" onclick="color(this)"></div>
        <div style="position:absolute;top:20%;left:43%;">Eraser</div>
        <div style="position:absolute;top:22%;left:45%;width:15px;height:15px;background:white;border:2px solid;" id="white" onclick="color(this)"></div>
        <img id="canvasimg" style="position:absolute;top:10%;left:52%;" style="display:none;">
        <input type="button" value="save" id="btn" size="30" onclick="save()" style="position:absolute;top:5%;left:10%;">
        <input type="button" value="clear" id="clr" size="23" onclick="erase()" style="position:absolute;top:5%;left:15%;">
    </body>
</html>

最佳答案

创建基于 Canvas 的绘图应用程序。

绘图应用程序没有那么多。听鼠标,当按钮按下时在鼠标位置绘制。

如果您想要一个响应式 Canvas 并且还包括撤消等,那么您需要从稍微复杂一点的级别开始。

绘图和展示。

首先,您应该将绘图与显示分开。这是通过创建一个包含绘图的屏幕外 Canvas 来完成的。它的大小是恒定的,并且可以由用户平移和缩放(甚至旋转)。

如果您正在创建线条或框,则使用屏幕外 Canvas 来保存绘图还可以让您在绘图上进行绘制。

一些有助于创建 Canvas 的功能

function createCanvas(width, height) {
  const c = document.createElement("canvas");
  c.width = width;
  c.height = height;
  c.ctx = c.getContext("2d");
  return c;
}
const drawing = createCanvas(512,512);

您可以将该 Canvas 绘制到显示 Canvas 上
ctx.drawImage(drawing,0,0);

该片段在 Canvas 的中心绘制了带有阴影和边框的图形,看起来不错。

鼠标处理程序

正确使用鼠标界面很重要。从 Canvas 监听鼠标事件有一些问题。当用户拖出 Canvas 时,您不再获得任何鼠标事件。这意味着当鼠标离开 Canvas 时,您需要停止绘图,因为您不知道鼠标返回时是否仍然向下。

要解决这个问题,您需要监听文档的鼠标事件。这将在按下按钮时捕获鼠标,允许用户在您仍然获得事件的同时将鼠标移动到屏幕上的任何位置。如果鼠标离开 Canvas 时上升,您仍然会收到该事件。

NOTE the stack overflow snippet window prevents mouse capture ATM (a recent change) so the above mouse behaviour is restricted to the iFrame containing the snippet.



您也不应该从鼠标事件中进行任何渲染。一些渲染操作可能很慢,比鼠标的更新速度慢得多。如果您从鼠标事件渲染,您将丢失鼠标事件。始终在鼠标事件监听器中执行尽可能少的代码。

在片段中,鼠标事件仅记录当前鼠标状态,如果鼠标按下并绘制,它将记录鼠标创建的路径。通过函数调用 requestAnimationFrame 同步到显示刷新率的单独循环负责渲染内容。它以大约 60fps 的速度运行。为了在没有发生任何事情时停止绘制,使用一个标志来指示显示需要更新updateDisplay当有更改时,您将其设置为 true updateDisplay=true;并且下次显示硬件准备好显示帧时,它将绘制所有更新的内容。

画一条线

一条线只是一组连接点。在片段中,我创建了一个线对象。它包含构成线条的点以及线条的宽度和颜色。

当鼠标按下时,我创建一个新的线对象并开始向它添加点。我标记显示需要更新,并在显示循环中通过其绘制方法在显示 Canvas 上绘制线条。

当鼠标向上移动时,我在绘图 Canvas 上画线。这样做可以让您对线条应用一些智能(片段很简单,对线条没有任何作用),例如使其沿其长度淡出。只有当鼠标抬起时,您才会知道它的长度。

在片段中,当鼠标启动时,我会丢弃该行,但如果您想要撤消操作,您可以将绘制的每一行保存在一个数组中。要撤消您只需清除绘图并重新绘制除撤消线之外的所有线。

线对象和相关代码。
// a point object creates point from x,y coords or object that has x,y
const point = (x, y = x.y + ((x = x.x) * 0)) => ({ x, y });

// function to add a point to the line
function addPoint(x, y) { this.points.push(point(x, y)); }

// draw a line on context ctx and adds offset.x, offset.y
function drawLine(ctx, offset) { 
  ctx.strokeStyle = this.color;
  ctx.lineWidth = this.width;
  ctx.lineJoin = "round";
  ctx.lineCap = "round";
  ctx.beginPath();
  var i = 0;
  while (i < this.points.length) {
    const p = this.points[i++];
    ctx.lineTo(p.x + offset.x, p.y + offset.y);
  }
  ctx.stroke();
}
// creates a new line object
function createLine(color, width) {
  return {
    points: [], // the points making up the line
    color,      // colour of the line
    width,      // width of the line
    add: addPoint,  // function to add a point
    draw: drawLine,  // function to draw the whole line
  };
}

示例片段

这段代码比我想要的要长一点。我已经为相关代码添加了评论,但如果您有任何问题,请在评论中提问,我会用更多信息更新答案。

// size of drawing and its starting background colour
const drawingInfo = {
  width: 384 ,
  height: 160,
  bgColor: "white",
}
const brushSizes = [1, 2, 3, 4, 5, 6, 7, 8];
const colors = "red,orange,yellow,green,cyan,blue,purple,white,gray,black".split(",");
var currentColor = "blue";
var currentWidth = 2;
var currentSelectBrush;
var currentSelectColor;
const colorSel = document.getElementById("colorSel");
colors.forEach((color, i) => {
  var swatch = document.createElement("span");
  swatch.className = "swatch";
  swatch.style.backgroundColor = color;
  if (currentColor === color) {
    swatch.className = "swatch highlight";
    currentSelectColor = swatch;
  } else {
    swatch.className = "swatch";
  }
  swatch.addEventListener("click", (e) => {
    currentSelectColor.className = "swatch";
    currentColor = e.target.style.backgroundColor;
    currentSelectColor = e.target;
    currentSelectColor.className = "swatch highlight";
  });
  colorSel.appendChild(swatch);
})
brushSizes.forEach((brushSize, i) => {
  var brush = document.createElement("canvas");
  brush.width = 16;
  brush.height = 16;
  brush.ctx = brush.getContext("2d");
  brush.ctx.beginPath();
  brush.ctx.arc(8, 8, brushSize / 2, 0, Math.PI * 2);
  brush.ctx.fill();
  brush.brushSize = brushSize;
  if (currentWidth === brushSize) {
    brush.className = "swatch highlight";
    currentSelectBrush = brush;
  } else {
    brush.className = "swatch";
  }

  brush.addEventListener("click", (e) => {
    currentSelectBrush.className = "swatch";
    currentSelectBrush = e.target;
    currentSelectBrush.className = "swatch highlight";
    currentWidth = e.target.brushSize;

  });
  colorSel.appendChild(brush);
})


const canvas = document.getElementById("can");
const mouse = createMouse().start(canvas, true);
const ctx = canvas.getContext("2d");
var updateDisplay = true; // when true the display needs updating
var ch, cw, w, h; // global canvas size vars


var currentLine;

var displayOffset = {
  x: 0,
  y: 0
};

// a point object creates point from x,y coords or object that has x,y
const point = (x, y = x.y + ((x = x.x) * 0)) => ({
  x,
  y
});
// function to add a point to the line
function addPoint(x, y) {
  this.points.push(point(x, y));
}

function drawLine(ctx, offset) { // draws a line
  ctx.strokeStyle = this.color;
  ctx.lineWidth = this.width;
  ctx.lineJoin = "round";
  ctx.lineCap = "round";
  ctx.beginPath();
  var i = 0;
  while (i < this.points.length) {
    const p = this.points[i++];
    ctx.lineTo(p.x + offset.x, p.y + offset.y);
  }
  ctx.stroke();
}

function createLine(color, width) {
  return {
    points: [],
    color,
    width,
    add: addPoint,
    draw: drawLine,
  };
}


// creates a canvas
function createCanvas(width, height) {
  const c = document.createElement("canvas");
  c.width = width;
  c.height = height;
  c.ctx = c.getContext("2d");
  return c;
}
// resize main display canvas and set global size vars
function resizeCanvas() {
  ch = ((h = canvas.height = innerHeight - 32) / 2) | 0;
  cw = ((w = canvas.width = innerWidth) / 2) | 0;
  updateDisplay = true;
}

function createMouse() {
  function preventDefault(e) { e.preventDefault() }
  const mouse = {
    x: 0,
    y: 0,
    buttonRaw: 0,
    prevButton: 0
  };
  const bm = [1, 2, 4, 6, 5, 3]; // bit masks for mouse buttons
  const mouseEvents = "mousemove,mousedown,mouseup".split(",");
  const m = mouse;
  // one mouse handler
  function mouseMove(e) {
    m.bounds = m.element.getBoundingClientRect();
    m.x = e.pageX - m.bounds.left - scrollX;
    m.y = e.pageY - m.bounds.top - scrollY;
    
    if (e.type === "mousedown") {
      m.buttonRaw |= bm[e.which - 1];
    } else if (e.type === "mouseup") {
      m.buttonRaw &= bm[e.which + 2];
    }
    // check if there should be a display update
    if (m.buttonRaw || m.buttonRaw !== m.prevButton) {
      updateDisplay = true;
    }
    // if the mouse is down and the prev mouse is up then start a new line
    if (m.buttonRaw !== 0 && m.prevButton === 0) { // starting new line
      currentLine = createLine(currentColor, currentWidth);
      currentLine.add(m); // add current mouse position
    } else if (m.buttonRaw !== 0 && m.prevButton !== 0) { // while mouse is down
      currentLine.add(m); // add current mouse position      
    }
    m.prevButton = m.buttonRaw; // remember the previous mouse state
    e.preventDefault();
  }
  // starts the mouse 
  m.start = function(element, blockContextMenu) {
    m.element = element;

    mouseEvents.forEach(n => document.addEventListener(n, mouseMove));
    if (blockContextMenu === true) {
      document.addEventListener("contextmenu", preventDefault)
    }
    return m
  }
  return m;
}
var cursor = "crosshair";
function update(timer) { // Main update loop
  cursor = "crosshair";
  globalTime = timer;
  // if the window size has changed resize the canvas
  if (w !== innerWidth || h !== innerHeight) {
    resizeCanvas()
  }
  if (updateDisplay) {
    updateDisplay = false;
    display(); // call demo code
  }
 
  ctx.canvas.style.cursor = cursor;
  requestAnimationFrame(update);
}
// create a drawing canvas.
const drawing = createCanvas(drawingInfo.width, drawingInfo.height);
// fill with white
drawing.ctx.fillStyle = drawingInfo.bgColor;
drawing.ctx.fillRect(0, 0, drawing.width, drawing.height);

// function to display drawing 
function display() {
  ctx.clearRect(0, 0, w, h);
  ctx.fillStyle = "rgba(0,0,0,0.25)";
  const imgX = cw - (drawing.width / 2) | 0;
  const imgY = ch - (drawing.height / 2) | 0;
  // add a shadow to make it look nice
  ctx.fillRect(imgX + 5, imgY + 5, drawing.width, drawing.height);

  // add outline
  ctx.strokeStyle = "black";
  ctx.lineWidth = "2";
  ctx.strokeRect(imgX, imgY, drawing.width, drawing.height);
  // draw the image
  ctx.drawImage(drawing, imgX, imgY);
  if (mouse.buttonRaw !== 0) {
    if (currentLine !== undefined) {
      currentLine.draw(ctx, displayOffset); // draw line on display canvas
      cursor = "none";
      updateDisplay = true; // keep updating 
    }
  } else if (mouse.buttonRaw === 0) {
    if (currentLine !== undefined) {
      currentLine.draw(drawing.ctx, {x: -imgX, y: -imgY }); // draw line on drawing
      currentLine = undefined;
      updateDisplay = true;
      // next line is a quick fix to stop a slight flicker due to the current frame not showing the line
      ctx.drawImage(drawing, imgX, imgY);

    }
  }
}

requestAnimationFrame(update);
#can {
  position: absolute;
  top: 32px;
  left: 0px;
  background-color: #AAA;
}

.colors {
  border: 1px solid black;
  display: inline-flex;
}

.swatch {
  min-width: 16px;
  min-height: 16px;
  max-width: 16px;
  border: 1px solid black;
  display: inline-block;
  margin: 2px;
  cursor: pointer;

}

.highlight {
  border: 1px solid red;
}
<canvas id="can"></canvas>
  <div class="colors" id="colorSel"></div>


更新 为了回应 OP 的评论,我添加了一个 HTML 版本,您应该可以将它(包括 <!DOCTYPE HTML></HTML> 在内的所有内容)复制并粘贴到 html 文档(例如,drawing.html)中,然后在支持 ES6 的浏览器。例如 Chrome、Firefox、Edge。

复制以下片段的内容。

<!DOCTYPE HTML>
<html>
<head>
    <style>
#can {
  position: absolute;
  top: 32px;
  left: 0px;
  background-color: #AAA;
}

.colors {
  border: 1px solid black;
  display: inline-flex;
}

.swatch {
  min-width: 16px;
  min-height: 16px;
  max-width: 16px;
  border: 1px solid black;
  display: inline-block;
  margin: 2px;
  cursor: pointer;

}

.highlight {
  border: 1px solid red;
}
    </style>
</head>

<body>
    <canvas id="can"></canvas>
    <div class="colors" id="colorSel"></div>

<script>
// size of drawing and its starting background colour
const drawingInfo = {
  width: 384 ,
  height: 160,
  bgColor: "white",
}
const brushSizes = [1, 2, 3, 4, 5, 6, 7, 8];
const colors = "red,orange,yellow,green,cyan,blue,purple,white,gray,black".split(",");
var currentColor = "blue";
var currentWidth = 2;
var currentSelectBrush;
var currentSelectColor;
const colorSel = document.getElementById("colorSel");
colors.forEach((color, i) => {
  var swatch = document.createElement("span");
  swatch.className = "swatch";
  swatch.style.backgroundColor = color;
  if (currentColor === color) {
    swatch.className = "swatch highlight";
    currentSelectColor = swatch;
  } else {
    swatch.className = "swatch";
  }
  swatch.addEventListener("click", (e) => {
    currentSelectColor.className = "swatch";
    currentColor = e.target.style.backgroundColor;
    currentSelectColor = e.target;
    currentSelectColor.className = "swatch highlight";
  });
  colorSel.appendChild(swatch);
})
brushSizes.forEach((brushSize, i) => {
  var brush = document.createElement("canvas");
  brush.width = 16;
  brush.height = 16;
  brush.ctx = brush.getContext("2d");
  brush.ctx.beginPath();
  brush.ctx.arc(8, 8, brushSize / 2, 0, Math.PI * 2);
  brush.ctx.fill();
  brush.brushSize = brushSize;
  if (currentWidth === brushSize) {
    brush.className = "swatch highlight";
    currentSelectBrush = brush;
  } else {
    brush.className = "swatch";
  }

  brush.addEventListener("click", (e) => {
    currentSelectBrush.className = "swatch";
    currentSelectBrush = e.target;
    currentSelectBrush.className = "swatch highlight";
    currentWidth = e.target.brushSize;

  });
  colorSel.appendChild(brush);
})


const canvas = document.getElementById("can");
const mouse = createMouse().start(canvas, true);
const ctx = canvas.getContext("2d");
var updateDisplay = true; // when true the display needs updating
var ch, cw, w, h; // global canvas size vars


var currentLine;

var displayOffset = {
  x: 0,
  y: 0
};

// a point object creates point from x,y coords or object that has x,y
const point = (x, y = x.y + ((x = x.x) * 0)) => ({
  x,
  y
});
// function to add a point to the line
function addPoint(x, y) {
  this.points.push(point(x, y));
}

function drawLine(ctx, offset) { // draws a line
  ctx.strokeStyle = this.color;
  ctx.lineWidth = this.width;
  ctx.lineJoin = "round";
  ctx.lineCap = "round";
  ctx.beginPath();
  var i = 0;
  while (i < this.points.length) {
    const p = this.points[i++];
    ctx.lineTo(p.x + offset.x, p.y + offset.y);
  }
  ctx.stroke();
}

function createLine(color, width) {
  return {
    points: [],
    color,
    width,
    add: addPoint,
    draw: drawLine,
  };
}


// creates a canvas
function createCanvas(width, height) {
  const c = document.createElement("canvas");
  c.width = width;
  c.height = height;
  c.ctx = c.getContext("2d");
  return c;
}
// resize main display canvas and set global size vars
function resizeCanvas() {
  ch = ((h = canvas.height = innerHeight - 32) / 2) | 0;
  cw = ((w = canvas.width = innerWidth) / 2) | 0;
  updateDisplay = true;
}

function createMouse() {
  function preventDefault(e) { e.preventDefault() }
  const mouse = {
    x: 0,
    y: 0,
    buttonRaw: 0,
    prevButton: 0
  };
  const bm = [1, 2, 4, 6, 5, 3]; // bit masks for mouse buttons
  const mouseEvents = "mousemove,mousedown,mouseup".split(",");
  const m = mouse;
  // one mouse handler
  function mouseMove(e) {
    m.bounds = m.element.getBoundingClientRect();
    m.x = e.pageX - m.bounds.left - scrollX;
    m.y = e.pageY - m.bounds.top - scrollY;
    
    if (e.type === "mousedown") {
      m.buttonRaw |= bm[e.which - 1];
    } else if (e.type === "mouseup") {
      m.buttonRaw &= bm[e.which + 2];
    }
    // check if there should be a display update
    if (m.buttonRaw || m.buttonRaw !== m.prevButton) {
      updateDisplay = true;
    }
    // if the mouse is down and the prev mouse is up then start a new line
    if (m.buttonRaw !== 0 && m.prevButton === 0) { // starting new line
      currentLine = createLine(currentColor, currentWidth);
      currentLine.add(m); // add current mouse position
    } else if (m.buttonRaw !== 0 && m.prevButton !== 0) { // while mouse is down
      currentLine.add(m); // add current mouse position      
    }
    m.prevButton = m.buttonRaw; // remember the previous mouse state
    e.preventDefault();
  }
  // starts the mouse 
  m.start = function(element, blockContextMenu) {
    m.element = element;

    mouseEvents.forEach(n => document.addEventListener(n, mouseMove));
    if (blockContextMenu === true) {
      document.addEventListener("contextmenu", preventDefault)
    }
    return m
  }
  return m;
}
var cursor = "crosshair";
function update(timer) { // Main update loop
  cursor = "crosshair";
  globalTime = timer;
  // if the window size has changed resize the canvas
  if (w !== innerWidth || h !== innerHeight) {
    resizeCanvas()
  }
  if (updateDisplay) {
    updateDisplay = false;
    display(); // call demo code
  }
 
  ctx.canvas.style.cursor = cursor;
  requestAnimationFrame(update);
}
// create a drawing canvas.
const drawing = createCanvas(drawingInfo.width, drawingInfo.height);
// fill with white
drawing.ctx.fillStyle = drawingInfo.bgColor;
drawing.ctx.fillRect(0, 0, drawing.width, drawing.height);

// function to display drawing 
function display() {
  ctx.clearRect(0, 0, w, h);
  ctx.fillStyle = "rgba(0,0,0,0.25)";
  const imgX = cw - (drawing.width / 2) | 0;
  const imgY = ch - (drawing.height / 2) | 0;
  // add a shadow to make it look nice
  ctx.fillRect(imgX + 5, imgY + 5, drawing.width, drawing.height);

  // add outline
  ctx.strokeStyle = "black";
  ctx.lineWidth = "2";
  ctx.strokeRect(imgX, imgY, drawing.width, drawing.height);
  // draw the image
  ctx.drawImage(drawing, imgX, imgY);
  if (mouse.buttonRaw !== 0) {
    if (currentLine !== undefined) {
      currentLine.draw(ctx, displayOffset); // draw line on display canvas
      cursor = "none";
      updateDisplay = true; // keep updating 
    }
  } else if (mouse.buttonRaw === 0) {
    if (currentLine !== undefined) {
      currentLine.draw(drawing.ctx, {x: -imgX, y: -imgY }); // draw line on drawing
      currentLine = undefined;
      updateDisplay = true;
      // next line is a quick fix to stop a slight flicker due to the current frame not showing the line
      ctx.drawImage(drawing, imgX, imgY);

    }
  }
}

requestAnimationFrame(update);

/* load and add image to the drawing. It may take time to load. */
function loadImage(url){
    const image = new Image();
    image.src = url;
    image.onload = function(){
        if(drawing && drawing.ctx){
            drawing.width = image.width;
            drawing.height = image.height;
            drawing.ctx.drawImage(image,0,0);
        };
    }

 }
 loadImage("/image/C7qq2.png?s=328&g=1");
        

</script>

</body>
</html>

关于javascript - 带有手绘线条的 HTML5 Canvas ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44934221/

相关文章:

c# - 检查矩形在 Canvas XAML 中是否相交

javascript - 弧形文本中字符串字符之间的间距不太正确。 (HTML Canvas )

optimization - 删除 HTML Canvas alpha channel

javascript - 为什么我们需要使用未压缩的文件进行开发?

html - 如何使用 chrome 检查 CSS 元素?

javascript - 自定义 toString 的推荐方式是什么?使用 Symbol.toStringTag 还是覆盖 toString?

html - PowerShell 将名称与用户电子邮件地址进行匹配,格式为 mailto

html - 点击菜单时如何防止打开子菜单的链接?

javascript - Framework7如何更改浏览器地址栏中的URL

javascript - 如何序列化 JavaScript 中的函数?