在 html 文本上“绘图”

Posted

技术标签:

【中文标题】在 html 文本上“绘图”【英文标题】:"Drawing" over html text 【发布时间】:2022-01-22 23:44:36 【问题描述】:

我需要做以下事情:拥有一个正常的网页,能够使用鼠标、触控笔或手指(如果是平板电脑或智能手机)在文本上“绘制形状”,然后使用形状,确定某些“动作”。

让我举个例子。想象一下***上的这个问题,然后我在ipad上“画”了一个围绕这一段的圆圈。圈子(或者圈子的尝试,手工画总是很不完善),“捕获”段落,所以我知道我会做一个动作“越过”段落,比如,选择所有的内部文本。

我的问题是:这可行吗?有没有这样做的图书馆?如果不是,哪些技术可以作为研究对象?

我一直在进行自己的研究,但没有什么能满足我的所有需求。例如,画布完全按照我的需要绘制,但不能在 html 页面上使用画布。它是页面上的另一个元素,而不是“包装”另一个元素。

【问题讨论】:

使用 CSS,您可以将画布放置在网页中的其他内容之上。然后,您将努力将画布上绘制的形状转换为特定文本。在我的脑海中,我不知道有任何 javascript API 可以以这种方式选择甚至识别文本;我认为文本的坐标是布局引擎内部的。 如果足够的话,您可以获得单个 HTML 元素的位置和大小。 【参考方案1】:

那么回答最初的问题:这可行吗?

是的

首先,您需要能够在页面上绘图。 HTML5 标记对此很有用,因为它允许我们非常轻松地在页面上绘制

其次,您需要能够识别页面上的哪些元素已被圈出。 javascript 函数 .getBoundingClientRect() 对此很有用,因为它告诉我们 x 和 y 位置以及宽度和高度。因为这些值是基于它们相对于文档边界框的位置(而不是父元素),所以我们可以将其与我们绘制的圆的坐标进行比较。

因此,有了这两个组件,我们可以将这样的东西组合在一起......(我承认这有点笨拙,但可以根据您的特定用例进行优化) :

var canvas, ctx, flag, coords, cntnr, dot_flag;
    
    function _InitDraw() 
      flag = false;
      coords = 
        pX: 0,
        cX: 0,
        pY: 0,
        cY: 0
      ;
      cntnr = 
        minX: document.body.offsetWidth,
        minY: document.body.offsetHeight,
        maxX: 0,
        maxY: 0
      ;
      dot_flag = false;

      canvas = document.createElement('canvas');
      canvas.id = "cnvTest";
      canvas.style.position = "fixed";
      canvas.style.top = 0;
      canvas.style.left = 0;
      document.body.appendChild(canvas);
      canvas.style.width = "100%";
      canvas.style.height = "100%";
      canvas.style.zIndex = 9001;
      ctx = canvas.getContext("2d");
      ctx.canvas.width = window.innerWidth;
      ctx.canvas.height = window.innerHeight;
  
      canvas.addEventListener("mousemove", function (e)  _Draw('move', e); , false);
      canvas.addEventListener("mousedown", function (e)  _Draw('down', e); , false);
      canvas.addEventListener("mouseup", function (e)  _Draw('up', e); , false);
      //canvas.addEventListener("mouseout", function (e)  _Draw('out', e); , false);
    
    
    function _Draw(action, e) 
      if(action == 'down') 
        coords.pX = coords.cX;
        coords.pY = coords.cY;
        coords.cX = e.pageX;
        coords.cY = e.pageY;
        
        if(coords.cX < cntnr.minX) cntnr.minX = coords.cX;
        if(coords.cY < cntnr.minY) cntnr.minY = coords.cY;
        if(coords.cX > cntnr.maxX) cntnr.maxX = coords.cX;
        if(coords.cY > cntnr.maxY) cntnr.maxY = coords.cY;

        flag = true;
        dot_flag = true;
        if(dot_flag) 
          ctx.beginPath();
          ctx.fillStyle = "black";
          ctx.lineCap = 'round';
          ctx.fillRect(coords.cX, coords.cY, 2, 2);
          ctx.closePath();
          dot_flag = false;
        
      
      if(action == 'up' || action == "out") 
        flag = false;
        _GetText();
      
      if(action == 'move') 
        if(flag) 
          coords.pX = coords.cX;
          coords.pY = coords.cY;
          coords.cX = e.pageX;
          coords.cY = e.pageY;

          if(coords.cX < cntnr.minX) cntnr.minX = coords.cX;
          if(coords.cY < cntnr.minY) cntnr.minY = coords.cY;
          if(coords.cX > cntnr.maxX) cntnr.maxX = coords.cX;
          if(coords.cY > cntnr.maxY) cntnr.maxY = coords.cY;
          
          ctx.beginPath();
          ctx.moveTo(coords.pX, coords.pY);
          ctx.lineTo(coords.cX, coords.cY);
          ctx.strokeStyle = "black";
          ctx.lineCap = 'round';
          ctx.lineWidth = 4;
          ctx.stroke();
          ctx.closePath();
        
      
    

    function _GetText() 
      // Find position and get closest element
      document.body.removeChild(canvas);

      var allEls = document.body.getElementsByTagName("*");
      for(var i = 0; i < allEls.length; i++) 
        var elBox = allEls[i].getBoundingClientRect();
        if(elBox.x > cntnr.minX && elBox.x+elBox.width < cntnr.maxX && elBox.y > cntnr.minY && elBox.y+elBox.height < cntnr.maxY) 
          // Do whatever you want with the element here.
          // I am adding an outline for confirmation of the circled elements.
          allEls[i].style.outline = "2px solid rgba(255, 255, 0, 0.8)";
          if(allEls[i].innerText !== null && allEls[i].innerText !== "undefined") 
            // Do something with the innerText here if you need the text from the element.
            console.log(allEls[i].innerText);
          
        
      
    

只是为了了解一些上下文,您需要先调用 _InitDraw() 函数。这将在页面上创建一个应该覆盖整个页面的 元素。

一旦您松开鼠标,它就会结束绘图模式并使用最小和最大位置值并循环浏览页面上的所有元素以查看是否有任何元素在这些范围内。

例如,此脚本会勾勒出这些对象的轮廓并将其中的任何文本记录到控制台。

*如果您想将其用于移动设备,则需要更改事件侦听器(touchstart、touchmove、touchend)。

**另外请注意,某些元素(例如任何“块”元素)的边界框可能比您视觉上看到的文本大得多。所以这个脚本不会“捕获”这些元素。当然,您可以修改脚本以包含绘图传递的任何元素,但这可能会导致混乱的结果。

【讨论】:

以上是关于在 html 文本上“绘图”的主要内容,如果未能解决你的问题,请参考以下文章

HTML5 Javascript:将文本保存到画布上?

随着 Alpha 的降低,玻璃背景上的绘图文本变得模糊

确定绘图中文本的实际宽度

Android绘图基础篇

如何在绘图表上显示悬停信息?

Matplotlib 绘图细节设置