jQuery实现拖动绿色div穿过图片透明区域、避让非透明区域
拖动元素穿透图片透明区域实现方案
核心思路
直接把带透明通道的图片层级盖在绿色div上面,天然就能实现「图片不透明的地方挡住下面的绿块、透明地方直接透出绿块」的效果,剩下要做的就是拖动的时候不让绿块跑到图片不透明的区域下面就行——这部分用Canvas读图片每个像素的透明度就能做校验。
实现步骤
- 先调样式层级:图片的
z-index设得比绿色div高,图片本身不要加背景色,保证透明区域能正常透出下层内容 - 图片加载完之后画到一个离屏Canvas上,把整张图的像素数据读出来存好,后面拖动的时候直接查对应位置的透明度就行。注意如果图片是存放在第三方域名下,要给图片标签加
crossOrigin="anonymous",不然Canvas读像素会报跨域错误,本地图片不用加这个配置 - 改原来的拖动逻辑:每次算出绿块要移动到的新位置后,先取绿块的中心点(用中心点判定交互最自然,要更精准也可以取四个角),看这个点是不是落在图片范围内:
- 点在图片外面,随便拖,不限制
- 点在图片里面,就查之前存的像素数据里这个位置的alpha通道值,值是0就是完全透明,允许移过去;值大于0就是不透明,直接不让绿块动到这个位置
改造完成的代码
// 初始化离屏Canvas预存图片像素数据 const img = document.querySelector('.target-img'); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); let imgPixelData = null; // 图片加载完成后读取全图像素 img.onload = function() { canvas.width = img.offsetWidth; canvas.height = img.offsetHeight; ctx.drawImage(img, 0, 0, canvas.width, canvas.height); imgPixelData = ctx.getImageData(0, 0, canvas.width, canvas.height).data; } // 跨域配置,第三方域名图片才需要开 img.crossOrigin = 'anonymous'; function handle_mousedown(e){ window.my_dragging = {}; my_dragging.pageX0 = e.pageX; my_dragging.pageY0 = e.pageY; my_dragging.elem = this; my_dragging.offset0 = $(this).offset(); // 提前缓存图片位置和尺寸,拖动的时候不用反复查DOM const imgOffset = $(img).offset(); const imgRect = { left: imgOffset.left, top: imgOffset.top, right: imgOffset.left + img.offsetWidth, bottom: imgOffset.top + img.offsetHeight } const boxWidth = $(this).outerWidth(); const boxHeight = $(this).outerHeight(); function handle_dragging(e){ const targetLeft = my_dragging.offset0.left + (e.pageX - my_dragging.pageX0); const targetTop = my_dragging.offset0.top + (e.pageY - my_dragging.pageY0); // 计算移动后绿块的中心点坐标 const centerX = targetLeft + boxWidth / 2; const centerY = targetTop + boxHeight / 2; let canMove = true; // 中心点落在图片范围内才需要校验透明度 if(centerX >= imgRect.left && centerX <= imgRect.right && centerY >= imgRect.top && centerY <= imgRect.bottom) { // 换算成图片内的相对坐标 const relX = Math.floor(centerX - imgRect.left); const relY = Math.floor(centerY - imgRect.top); // getImageData返回的数组按R、G、B、A顺序存像素值,alpha值的索引是 (y坐标*图宽 + x坐标)*4 +3 const alphaPos = (relY * canvas.width + relX) * 4 + 3; // 非透明区域禁止移动 if(imgPixelData[alphaPos] > 0) canMove = false; } if(canMove) { $(my_dragging.elem).offset({top: targetTop, left: targetLeft}); } } function handle_mouseup(e){ $('body') .off('mousemove', handle_dragging) .off('mouseup', handle_mouseup); } $('body') .on('mouseup', handle_mouseup) .on('mousemove', handle_dragging); } $('.box').mousedown(handle_mousedown);
必要的基础样式
.target-img { position: absolute; z-index: 10; /* 按实际需求设置图片的展示位置 */ left: 100px; top: 100px; } .box { position: absolute; z-index: 1; width: 50px; height: 50px; background: #00d000; cursor: move; }
精度优化:如果要完全避免绿块边缘卡进图片不透明区域,可以把单中心点校验改成校验绿块四个角的透明度,只有四个角对应位置都是透明的时候才允许移动;对精度要求更高的场景可以给绿块覆盖区域做均匀采样点校验。
内容的提问来源于stack exchange,提问作者Maman
相关产品推荐
相关产品推荐

