【问题标题】:Place objects on a defined shape将对象放置在定义的形状上
【发布时间】:2015-02-17 14:46:38
【问题描述】:

我正在寻找一种方法,允许仅在图像的区域内进行拖放。

那么,如果图片如下:

我可以选择在顶部添加另一个图层,其中包含一些文本,但是这个新图层不能去任何地方,只能在该图像的形状内。

所以,经过一番研究,我仍然想知道如何才能实现这样的目标? 我知道我可以使用maparea 将现有元素映射到图像上,但是如何添加仅适合该地图的新元素?有什么想法吗?

【问题讨论】:

    标签: css html canvas area


    【解决方案1】:

    为了说明您的解决方案,假设您要在云中滴一滴雨滴,并确保所有雨滴像素都完全在云中...

    那么你的问题的答案需要问这个问题:

    所有不透明的雨滴像素都完全在云中吗?

    要回答这个问题,您必须将雨滴上的每个像素与下方的每个像素进行比较。

    • 如果雨滴像素是透明的,则忽略该像素,因为这部分雨滴无论如何都是透明的。
    • 如果雨滴像素是不透明的,而下面的像素是透明的,那么这个雨滴像素不包含在云中
    • 如果雨滴像素和下方像素都是不透明的,则此雨滴像素包含在云中

    您可以通过在画布上绘制它们的图像然后请求getImageData 来获取有关雨滴和云的所需透明度信息。 'getImageData' 返回关于画布上每个像素的红色、绿色、蓝色和 alpha 信息。要回答这个问题,我们只需要 alpha 信息。

    这是带注释的代码和一个演示:

    // canvas related variables
    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var cw=canvas.width;
    var ch=canvas.height;
    var offsetX,offsetY;
    
    // load the cloud and raindrop images
    var cloudmap,rainmap;
    var rain=new Image();
    rain.crossOrigin='anonymous';
    rain.onload=start;
    rain.src='https://dl.dropboxusercontent.com/u/139992952/raindrop1.png';
    var cloud=new Image();
    cloud.crossOrigin='anonymous';
    cloud.onload=start;
    cloud.src="https://dl.dropboxusercontent.com/u/139992952/cloud.png";
    var cloud1=new Image();
    cloud1.crossOrigin='anonymous';
    cloud1.onload=start;
    cloud1.src="https://dl.dropboxusercontent.com/u/139992952/multple/cloud1.png";
    var imageCount=3;
    
    function start(){
      if(--imageCount>0){return;}
    
      // resize the canvas to the size of the cloud
      // and draw the cloud on the canvas
      cw=canvas.width=cloud.width;
      ch=canvas.height=cloud.height;
      draw();
    
      // create a transparency map of the cloud
      cloudmap={
        width:cloud.width,
        height:cloud.height,
        map:transparencyMap(cloud),
      };   
    
      // create a transparency map of the raindrop
      rainmap={
        width:rain.width,
        height:rain.height,
        map:transparencyMap(rain),
      }
    
      // listen for mousemove events
      $("#canvas").mousemove(function(e){handleMouseMove(e);});
    
      // listen for window scroll events
      calcCanvasOffset();
      $(window).scroll(function(){ calcCanvasOffset(); });
    
    }
    
    
    function transparencyMap(img){
      // create a temp canvas sized to the img size
      var c=document.createElement('canvas');
      var cctx=c.getContext('2d');
      c.width=img.width;
      c.height=img.height;
      // draw the img on the canvas
      cctx.drawImage(img,0,0);
      // get the pixel data for every pixel on the canvas
      var data=cctx.getImageData(0,0,c.width,c.height).data;
      // create an array that reports the status 
      // of every pixel on the canvas
      // (status: true if opaque, false if transparent)
      var map=[];
      for(var i=0;i<data.length;i+=4){
        map.push(data[i+3]>250);
      }
      return(map);
    }
    
    
    function draw(mouseX,mouseY,isContained){
      // draw the cloud
      ctx.clearRect(0,0,cw,ch);
      if(isContained){
        // draw the blue cloud indicating the raindrop is not fully contained
        ctx.drawImage(cloud,0,0);
      }else{
        // draw the yellow cloud indicating the raindrop is fully contained
        ctx.drawImage(cloud1,0,0);
      }
      // if the mouse position was supplied
      if(mouseX){
        ctx.drawImage(rain,mouseX-rain.width/2,mouseY-rain.height/2);
      }
    }
    
    
    function AcontainsB(ax,ay,amap,bx,by,bmap){
      // set a flag indicating of the raindrop is fully contained in the cloud
      var isContained=true;
    
      // calc the relative position of the raindrop vs cloud in the canvas
      var deltaX=bx-ax;
      var deltaY=by-ay;
    
      // test every pixel of B against A
      // if B is opaque and a is not opaque then B is not contained by A
      var y=0;
      while(isContained && y<bmap.height){
        var x=0;
        while(isContained && x<bmap.width){
          // calc the map array indexes for the cloud(A) & raindrop(B)
          var mapIndexA=(y+deltaY)*amap.width+(x+deltaX);
          var mapIndexB=y*bmap.width+x;
          // if the raindrop is opaque at this pixel
          if(bmap.map[mapIndexB]){
    
            // ...and if this pixel is off canvas
            if(mapIndexA<0 || mapIndexA>=amap.map.length){
              // ...then the raindrop is not in the cloud at this pixel
              isContained=false;
              // ...or if the pixel under the raindrop is transparent 
            }else if(!amap.map[mapIndexA]){
              // ...then the raindrop is not in the cloud at this pixel
              isContained=false;
            }
          }            
          x++;
        }
        y++;
      }
      return(isContained);
    }
    
    
    function handleMouseMove(e){
      // tell the browser we're handling this event
      e.preventDefault();
      e.stopPropagation();
    
      // get the current mouse position
      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);
    
      // calc the top-left corner of the raindrop image
      var rainX=parseInt(mouseX-rain.width/2);
      var rainY=parseInt(mouseY-rain.height/2);
    
      // ask if the raindrop is fully contained in the cloud
      var isContained=AcontainsB(0,0,cloudmap,rainX,rainY,rainmap);
    
    
      // redraw the cloud & raindrop
      draw(mouseX,mouseY,isContained);
    
    }
    
    
    // recalc the canvas offsetX & offsetY
    function calcCanvasOffset(){
      var BB=canvas.getBoundingClientRect();
      offsetX=BB.left;
      offsetY=BB.top;        
    }
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <h4>Use the mouse to drag the raindrop over the canvas<br>The cloud turns blue if the rain is fully inside the cloud</h4>
    <canvas id="canvas" width=600 height=500></canvas>

    您可以使用此“透明度映射”来测试您可以绘制到画布中的任何内容,包括图像和文本。注意文字是用context.fillText绘制的。

    如果您将文本元素拖放到画布外部(可能使用 jqueryUI 或本机“可拖动”),则必须:

    1. 获取 drop 的 x,y 位置。

    2. 获取拖放元素的文本内容。

    3. 创建一个包含文本的临时画布。这样做...

      function textToCanvas(text,fontsize,fontface){
          var c=document.createElement('canvas');
          var cctx=c.getContext('2d');
          cctx.font=fontsize+'px '+fontface;
          var textWidth=cctx.measureText(text).width;
          c.width=textWidth;
          c.height=fontsize+4;
          cctx.font=fontsize+'px '+fontface;
          cctx.textBaseline='top';
          cctx.fillText(text,0,0);
          return(c);
      }
      
    4. 像使用图像一样使用临时画布来创建透明度贴图。这是可能的,因为画布将接受另一个画布作为其图像源。

    祝你的项目好运。

    [来自 cmets 的其他问题]

    其他问题:

    “当你把雨滴放在某个地方时,它会有什么反应? 云(保存它的位置),然后你尝试添加一些 在它之上?” 换一种方式说:“我如何测试是否有 2 个对象 重叠?”

    答案:您可以再次使用透明度贴图来测试两个对象是否重叠。创建另一个测试 (AintersectsB),测试 A 中的任何像素是否不透明,而 B 中的关联像素也是不透明的。您可以从AcontainsB 开始并对其进行修改以创建AintersectsB 测试。

    其他问题:

    “我如何保存并在以后恢复放置的对象位置?”

    答案: 因为画布不记得它画了什么,所以你必须记住它。这通常是通过为每个删除的项目创建一个 javascript 对象并将所有这些对象保存在一个数组中来完成的。这样,如果您需要将位置保存在服务器上,您可以使用JSON.stringify 将对象数组转换为字符串并将该字符串发送到服务器以保存在数据库(或文件)中。为了重新创建您的工作,服务器从数据库中提取字符串并将其发送到浏览器。浏览器使用JSON.parse 将字符串转换回javascript 对象数组。然后,您可以完全按照使用对象中的信息重新绘制场景。

    【讨论】:

    • 我真的无法期待更清晰和详细的答案。绝对很棒的视角,但是,当你将雨滴放在云中的某个地方(保存它的位置),然后你尝试在它上面添加一些东西时,这将如何反应?我现在正在测试你的脚本,但非常感谢你的意见。
    • 仍在经历它,但只是想问...您如何建议我在拖动的元素上实现保存并记住位置,并且在刷新时,我将能够将它们拉回来?
    • 不客气!我已添加到我的帖子中以回答您的其他问题。干杯!
    猜你喜欢
    • 1970-01-01
    • 2017-04-28
    • 1970-01-01
    • 2014-10-14
    • 2012-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多