是否可以在 A 帧场景中使用 HTML 图像映射?
Is it possible to use HTML image maps within an A-Frame scene?
场景如下:
我有一张世界地图的 360º 全景图,运行 在 A 帧上
我希望每个大陆都可以点击,重定向到相关的维基百科页面。
我试过的:
- 我尝试像在正常项目中一样使用
<map>
标签,但是
没有结果。
- 我也看了文档,没找到
与此相关。
我的问题:
我的目标有可能实现吗?如果可以,怎么做?
我的代码:
注:目前只有非洲大陆有坐标。
<script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
<a-scene>
<a-entity camera look-controls="reverseMouseDrag: true"></a-entity>
<a-sky id="image-360" radius="10" src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Equirectangular_projection_SW.jpg/1920px-Equirectangular_projection_SW.jpg" usemap="#image_map"></a-sky>
<map name="image_map">
<area alt="Africa" title="Africa" href="https://en.wikipedia.org/wiki/Africa" coords="920,296 871,351 873,424 916,461 979,449 1014,463 1005,490 1032,540 1020,579 1056,663 1081,667 1114,655 1146,600 1175,561 1165,504 1220,451 1228,422 1191,426 1124,314 1063,308 1054,324 1014,302 1016,284 963,290" shape="polygon">
</map>
</a-scene>
<map>
标签将区域放置在图像上方。它不会像那样将区域投影到三维对象上。
我建议您 re-use 在 a-frame 自定义组件中的 <map>
标记中定义的区域。
tldr:
我制作的 a component 似乎可以完成这项工作,而且使用起来相当简单:
<a-image material="src: #texture" asourcemap="#some-map"></a-image>
<!-- somewhere else -->
<map id="some-map">
<area shape="rect" alt="rectangle" coords="0,0, 50,50" href="rect.html">
<area shape="polygon" alt="poly" coords="0,0, 40,50, 25,0">
</map>
它应该与 href
属性配合使用,但 <a-image>
也会发出带有点击区域名称的信号。
您可以看到它使用 3D 平面和圆柱体 here。
tldr 结束
0。收集 <map>
数据
简单解析。获取 <map>
元素,遍历所有子项,并使用 getAttribute()
:
收集他们的数据
var map = document.querySelector(selector);
for (let area of map.children) {
// area.getAttribute("href") - href attribute
// area.getAttribute("alt") - alt name
// area.getAttribute("coords") - coordinates array.
}
保存起来以备后用。坐标是逗号分隔的字符串,因此您可能需要 parseInt()
它们,管理顺序(即 [[x1,x1], [x2,y2], [x3, y3]]
)
1.使 a-frame 实体可交互
对点击做出反应,更重要的是 - 检查点击发生的位置:
this.el.addEventListener("click", evt => {
var UVPoint = evt.detail.intersection.uv
})
UV mapping 将帮助我们确定单击了纹理上的哪个点。 UV 范围从 <0, 1>,所以我们需要 re-scale UVPoint:
// may need waiting for "model-loaded"
let mesh = this.el.getObject3D("mesh")
// this may not be available immidiately
let image = mesh.material.map.image
let x_on_image = UVPoint * image.width
// the y axis goes from <1, 0>
let y_on_image = image.height - UVPoint * image.heigth
嘿,我们得到了区域坐标和点坐标!
只剩下一件事了:
2。确定某个区域是否被点击
不需要re-invent这里的轮子。 This SO question on checking if a point is inside a polygon 有一个简单的 inside(point, polygon)
函数。其实我们什么都有,所以我们做的最后一件事是:
- 遍历多边形
- 检查点击点是否在任何多边形内
- 如果肯定 - 做你的事
像这样:
var point = [x_on_texture, y_on_texture]
for (var i = 0; i < polygons.length; i++) {
// polygons need to be [[x1, y1], [x2, y2],...[xn, yn]] here
if (inside(point, polygons[i]) {
console.log("polygon", i, "clicked!")
}
}
如果您跳过了 tldr 部分 - 上述步骤合并在 this component and used in this example
中
3。老掉牙的尝试
另一种方法可以是:
- 收到对 a-frame 实体的点击
- 像 1
一样抓取点击的坐标
- 隐藏场景
- 查看哪个
<area>
在document.elementFromPoint(x, y);
的坐标处。
- 展示现场
- 使用
document.createEvent("MouseEvent");
创建鼠标事件
- 在
<area>
元素上发送它。
即使在我的手机上,隐藏/显示技巧也非常有效 phone。我真的很惊讶场景没有闪烁,没有冻结,甚至没有减速。
但是 document.elementFromPoint(x, y);
不能与 firefox 一起工作,并且可能任何让它工作的尝试都比 0-[=84= 更耗时]2 步。我也相信陷阱会变得更大 case-dependant.
无论如何,这是 old-answer 组件:
/* SETUP
<a-scene>
<a-image press-map>
</a-scene>
<image id="image" sourcemap="map">
<map name="map">
<area ...>
</map>
*/
AFRAME.registerComponent("press-map", {
init: function() {
// the underlying image
this.img = document.querySelector("#image")
// react on clicks
this.el.addEventListener("click", evt => {
// get the point on the UV
let uvPoint = evt.detail.intersection.uv
// the y is inverted
let pointOnImage = {
x: uvPoint.x * this.img.width,
y: this.img.height - uvPoint.y * this.img.height
}
// the ugly show-hide bits
this.el.sceneEl.style.display = "none";
this.img.style.display = "block";
// !! grab the <area> at the (x,y) position
var el = document.elementFromPoint(pointOnImage.x, pointOnImage.y);
this.el.sceneEl.style.display="block"
this.img.style.display="none"
// create and dispatch the event
var ev = document.createEvent("MouseEvent");
ev.initMouseEvent(
"click",
true /* bubble */, false /* cancelable */,
window, null,
x, y, 0, 0, /* coordinates */
false, false, false, false, /* modifier keys */
0 /*left*/, null
);
el.dispatchEvent(ev);
}
}
})
场景如下:
我有一张世界地图的 360º 全景图,运行 在 A 帧上
我希望每个大陆都可以点击,重定向到相关的维基百科页面。
我试过的:
- 我尝试像在正常项目中一样使用
<map>
标签,但是 没有结果。 - 我也看了文档,没找到 与此相关。
我的问题:
我的目标有可能实现吗?如果可以,怎么做?
我的代码:
注:目前只有非洲大陆有坐标。
<script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
<a-scene>
<a-entity camera look-controls="reverseMouseDrag: true"></a-entity>
<a-sky id="image-360" radius="10" src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Equirectangular_projection_SW.jpg/1920px-Equirectangular_projection_SW.jpg" usemap="#image_map"></a-sky>
<map name="image_map">
<area alt="Africa" title="Africa" href="https://en.wikipedia.org/wiki/Africa" coords="920,296 871,351 873,424 916,461 979,449 1014,463 1005,490 1032,540 1020,579 1056,663 1081,667 1114,655 1146,600 1175,561 1165,504 1220,451 1228,422 1191,426 1124,314 1063,308 1054,324 1014,302 1016,284 963,290" shape="polygon">
</map>
</a-scene>
<map>
标签将区域放置在图像上方。它不会像那样将区域投影到三维对象上。
我建议您 re-use 在 a-frame 自定义组件中的 <map>
标记中定义的区域。
tldr:
我制作的 a component 似乎可以完成这项工作,而且使用起来相当简单:
<a-image material="src: #texture" asourcemap="#some-map"></a-image>
<!-- somewhere else -->
<map id="some-map">
<area shape="rect" alt="rectangle" coords="0,0, 50,50" href="rect.html">
<area shape="polygon" alt="poly" coords="0,0, 40,50, 25,0">
</map>
它应该与 href
属性配合使用,但 <a-image>
也会发出带有点击区域名称的信号。
您可以看到它使用 3D 平面和圆柱体 here。
tldr 结束
0。收集 <map>
数据
简单解析。获取 <map>
元素,遍历所有子项,并使用 getAttribute()
:
var map = document.querySelector(selector);
for (let area of map.children) {
// area.getAttribute("href") - href attribute
// area.getAttribute("alt") - alt name
// area.getAttribute("coords") - coordinates array.
}
保存起来以备后用。坐标是逗号分隔的字符串,因此您可能需要 parseInt()
它们,管理顺序(即 [[x1,x1], [x2,y2], [x3, y3]]
)
1.使 a-frame 实体可交互
对点击做出反应,更重要的是 - 检查点击发生的位置:
this.el.addEventListener("click", evt => {
var UVPoint = evt.detail.intersection.uv
})
UV mapping 将帮助我们确定单击了纹理上的哪个点。 UV 范围从 <0, 1>,所以我们需要 re-scale UVPoint:
// may need waiting for "model-loaded"
let mesh = this.el.getObject3D("mesh")
// this may not be available immidiately
let image = mesh.material.map.image
let x_on_image = UVPoint * image.width
// the y axis goes from <1, 0>
let y_on_image = image.height - UVPoint * image.heigth
嘿,我们得到了区域坐标和点坐标! 只剩下一件事了:
2。确定某个区域是否被点击
不需要re-invent这里的轮子。 This SO question on checking if a point is inside a polygon 有一个简单的 inside(point, polygon)
函数。其实我们什么都有,所以我们做的最后一件事是:
- 遍历多边形
- 检查点击点是否在任何多边形内
- 如果肯定 - 做你的事
像这样:
var point = [x_on_texture, y_on_texture]
for (var i = 0; i < polygons.length; i++) {
// polygons need to be [[x1, y1], [x2, y2],...[xn, yn]] here
if (inside(point, polygons[i]) {
console.log("polygon", i, "clicked!")
}
}
如果您跳过了 tldr 部分 - 上述步骤合并在 this component and used in this example
中3。老掉牙的尝试
另一种方法可以是:
- 收到对 a-frame 实体的点击
- 像 1 一样抓取点击的坐标
- 隐藏场景
- 查看哪个
<area>
在document.elementFromPoint(x, y);
的坐标处。 - 展示现场
- 使用
document.createEvent("MouseEvent");
创建鼠标事件
- 在
<area>
元素上发送它。
即使在我的手机上,隐藏/显示技巧也非常有效 phone。我真的很惊讶场景没有闪烁,没有冻结,甚至没有减速。
但是 document.elementFromPoint(x, y);
不能与 firefox 一起工作,并且可能任何让它工作的尝试都比 0-[=84= 更耗时]2 步。我也相信陷阱会变得更大 case-dependant.
无论如何,这是 old-answer 组件:
/* SETUP
<a-scene>
<a-image press-map>
</a-scene>
<image id="image" sourcemap="map">
<map name="map">
<area ...>
</map>
*/
AFRAME.registerComponent("press-map", {
init: function() {
// the underlying image
this.img = document.querySelector("#image")
// react on clicks
this.el.addEventListener("click", evt => {
// get the point on the UV
let uvPoint = evt.detail.intersection.uv
// the y is inverted
let pointOnImage = {
x: uvPoint.x * this.img.width,
y: this.img.height - uvPoint.y * this.img.height
}
// the ugly show-hide bits
this.el.sceneEl.style.display = "none";
this.img.style.display = "block";
// !! grab the <area> at the (x,y) position
var el = document.elementFromPoint(pointOnImage.x, pointOnImage.y);
this.el.sceneEl.style.display="block"
this.img.style.display="none"
// create and dispatch the event
var ev = document.createEvent("MouseEvent");
ev.initMouseEvent(
"click",
true /* bubble */, false /* cancelable */,
window, null,
x, y, 0, 0, /* coordinates */
false, false, false, false, /* modifier keys */
0 /*left*/, null
);
el.dispatchEvent(ev);
}
}
})