【发布时间】:2015-02-17 14:46:38
【问题描述】:
我正在寻找一种方法,允许仅在图像的区域内进行拖放。
那么,如果图片如下:
我可以选择在顶部添加另一个图层,其中包含一些文本,但是这个新图层不能去任何地方,只能在该图像的形状内。
所以,经过一番研究,我仍然想知道如何才能实现这样的目标?
我知道我可以使用map 和area 将现有元素映射到图像上,但是如何添加仅适合该地图的新元素?有什么想法吗?
【问题讨论】:
我正在寻找一种方法,允许仅在图像的区域内进行拖放。
那么,如果图片如下:
我可以选择在顶部添加另一个图层,其中包含一些文本,但是这个新图层不能去任何地方,只能在该图像的形状内。
所以,经过一番研究,我仍然想知道如何才能实现这样的目标?
我知道我可以使用map 和area 将现有元素映射到图像上,但是如何添加仅适合该地图的新元素?有什么想法吗?
【问题讨论】:
为了说明您的解决方案,假设您要在云中滴一滴雨滴,并确保所有雨滴像素都完全在云中...
那么你的问题的答案需要问这个问题:
所有不透明的雨滴像素都完全在云中吗?
要回答这个问题,您必须将雨滴上的每个像素与下方的每个像素进行比较。
您可以通过在画布上绘制它们的图像然后请求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 或本机“可拖动”),则必须:
获取 drop 的 x,y 位置。
获取拖放元素的文本内容。
创建一个包含文本的临时画布。这样做...
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);
}
像使用图像一样使用临时画布来创建透明度贴图。这是可能的,因为画布将接受另一个画布作为其图像源。
祝你的项目好运。
[来自 cmets 的其他问题]
其他问题:
“当你把雨滴放在某个地方时,它会有什么反应? 云(保存它的位置),然后你尝试添加一些 在它之上?” 换一种方式说:“我如何测试是否有 2 个对象 重叠?”
答案:您可以再次使用透明度贴图来测试两个对象是否重叠。创建另一个测试 (AintersectsB),测试 A 中的任何像素是否不透明,而 B 中的关联像素也是不透明的。您可以从AcontainsB 开始并对其进行修改以创建AintersectsB 测试。
其他问题:
“我如何保存并在以后恢复放置的对象位置?”
答案: 因为画布不记得它画了什么,所以你必须记住它。这通常是通过为每个删除的项目创建一个 javascript 对象并将所有这些对象保存在一个数组中来完成的。这样,如果您需要将位置保存在服务器上,您可以使用JSON.stringify 将对象数组转换为字符串并将该字符串发送到服务器以保存在数据库(或文件)中。为了重新创建您的工作,服务器从数据库中提取字符串并将其发送到浏览器。浏览器使用JSON.parse 将字符串转换回javascript 对象数组。然后,您可以完全按照使用对象中的信息重新绘制场景。
【讨论】: