2.3.11 2D/3D界面
ThingJS 是一个先进的 PaaS 开发平台,开发者可以方便、安全地基于云端的各种工具组件随时随地进行开发。
3D 界面
ThingJS 主要提供 Marker
物体和 WebView
物体以支持 3D 空间界面。
创建 Marker
Marker 物体可以添加一个图片放置到你希望的位置,也可以将这个图片作为孩子添加到对象身上,随着对象一同移动。Marker 默认是受距离远近影响,呈现近大远小的 3D 效果,也会在 3D 空间中实现前后遮挡。
app.create({
type: "Marker",
offset: [0, 2, 0],
size: [4, 4],
url: "https://thingjs.com/static/images/warning1.png",
parent: app.query("car01")[0]
});
效果如下图所示:
创建 WebView 物体
我们可以使用 WebView 物体,将其他网站或者页面的内容嵌到 3D 中。
var webView01 = app.create({
type: 'WebView',
url: 'https://www.thingjs.com',
position: [10, 13, -5],
width: 1920 * 0.01,
height: 1080 * 0.01,
domWidth: 1920,
domHeight: 1080
});
效果如下图所示:
应用示例
Marker:可以将图标、Canvas绘制的图片,展现在3D场景中或绑定在3D物体上。
function createTextCanvas(text, canvas) {
if (!canvas) {
canvas = document.createElement("canvas");
canvas.width = 64;
canvas.height = 64;
}
const ctx = canvas.getContext("2d");
ctx.fillStyle = "rgb(32, 32, 256)";
ctx.beginPath();
ctx.arc(32, 32, 30, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = "rgb(255, 255, 255)";
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(32, 32, 30, 0, Math.PI * 2);
ctx.stroke();
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.font = "32px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(text, 32, 32);
return canvas;
}
app.on('load', function (ev) {
var marker = app.create({
type: "Marker",
offset: [0, 2, 0],
size: 3,
canvas: createTextCanvas('100'),
parent: app.query('car02')[0]
}).on('click', function (ev) {
var txt = Math.floor(Math.random() * 100);
ev.object.canvas = createTextCanvas(txt, ev.object.canvas)
})
})
运行结果见下图,在 Marker 上点击时,会改变标记上的数字:
常用属性
下表中列出的是创建Marker
属性。
属性名称 | 类型 | 说明 | 备注 |
---|---|---|---|
type | String | 通知系统创建 Marker 物体 | |
offset | Array | 设置自身坐标系下偏移量为[0, 2, 0] | 与 localPosition 二选一 |
localPosition | Array | 设置自身坐标系下偏移量为[0, 2, 0] | 与 offset 二选一 |
size | Array | 设置 Marker 物体大小,也可以添单独数字如 4,等同于[4,4],大小是以米计算的 | |
url | String | 图片的 url | |
parent | Object | 指定 Marker 的父物体 | |
keepSize | Boolean | 控制是否受距离远近影响,呈现近大远小的 3D 效果。如果设置 true,表示保持大小,不随距离近大远小,此时 size 的单位是屏幕的像素点 | optional |
canvas | Object | 接收 canvas 作为贴图显示 | optional |
下表中列出的是创建 WebView 属性。
属性名称 | 类型 | 说明 |
---|---|---|
type | String | 通知系统创建 WebView 物体 |
parent | Object | 指定 WebView 的父物体 |
url | String | 图片的 url |
width | String | 3D 中实际宽度 单位 米 |
height | String | 3D 中实际高度 单位 米 |
domWidth | String | 页面宽度 单位 px |
domHeight | String | 页面高度 单位 px |
2D 界面
JS 编写原生界面
var template =
`<div style='position:absolute;left:20px;top:20px;padding: 8px;width:100px;text-align: center;background: rgba(0,0,0,0.5);'>
<p id="p1" style='color:white'>Hello World!</p>
<button style='margin:4px;padding:4px' onclick='changeLevel()'>Into Level</button></div>`;
// 插入到 ThingJS 内置的 2D 界面 div 中
$('#div2d').append($(template));
注意事项
ThingJS内置了
div2d
和div3d
元素,创建2D界面时需要将元素插入到div2d
。
创建 UIAnchor
UIAnchor
可以把 2D html 界面连接到 3D 物体上,跟随 3D 物体移动。
var uiAnchor = app.create({
type: "UIAnchor",
parent: app.query("car02")[0],
element: document.getElementById("XXXX"),
localPosition: [0, 2, 0],
pivotPixel: [-16, 109]
});
// 删除UIAnchor
// uiAnchor.destroy();
// 控制显示
// uiAnchor.visible = true;
效果如下图所示:
注意事项
删除后,其对应的 panel 也会被删除。
快捷界面库
THING.widget 是一个支持动态数据绑定的轻量级界面库。
可通过界面库中的 Panel 组件创建一个面板,并可向该面板中添加文本、数字、单选框、复选框等其他组件。
效果如下:
创建面板
// 创建面板
var panel = new THING.widget.Panel({
// 设置面板样式
template: 'default',
// 角标样式
cornerType: "none",
// 设置面板宽度
width: "300px",
// 是否有标题
hasTitle: true,
// 设置标题名称
titleText: "我是标题",
// 面板是否允许有关闭按钮
closeIcon: true,
// 面板是否支持拖拽功能
dragable: true,
// 面板是否支持收起功能
retractable: true,
// 设置透明度
opacity: 0.9,
// 设置层级
zIndex: 99
});
// 面板数据
var dataObj = {
pressure: "0.14MPa",
temperature: "21°C",
checkbox: { 设备1: false, 设备2: false, 设备3: true, 设备4: true },
radio: "摄像机01",
open1: true,
height: 10,
maxSize: 1.0,
iframe: "https://www.thingjs.com",
progress: 1,
img: "https://www.thingjs.com/guide/image/new/logo2x.png",
button: false
};
// 添加组件
var press = panel.addString(dataObj, 'pressure').caption('水压').isChangeValue(true);
var height = panel.addNumber(dataObj, 'height').caption('高度');
var maxSize = panel.addNumberSlider(dataObj, 'maxSize').step(0.25).min(1).max(10);
var open1 = panel.addBoolean(dataObj, 'open1').caption('开关01');
var radio = panel.addRadio(dataObj, 'radio', ['摄像机01', '摄像机02']);
var check = panel.addCheckbox(dataObj, 'checkbox').caption({ "设备2": "设备2(rename)" });
var iframe = panel.addIframe(dataObj, 'iframe').caption('视屏');
var img = panel.addIframe(dataObj, 'img').caption('图片');
var button = panel.addImageBoolean(dataObj, 'button').caption('仓库编号').url('https://www.thingjs.com/static/images/example/icon.png');
创建图标按钮面板
创建图片按钮面板:
var toolbar = new THING.widget.Panel({ width: "163px",captionPos:'hover'});
toolbar.position = ['100%', 0];
toolbar.positionOrigin = "TR";
//绑定物体
var dataObj = {
warehouseCode: true,
temperature: false,
humidity: false,
statistics: false,
status: false,
insect: false,
cerealsReserve: false,
video: false,
cloud: true,
orientation: false
}
var baseURL = "https://www.thingjs.com/static/images/sliohouse/";
var button0 = toolbar.addImageBoolean(dataObj, 'warehouseCode').caption('仓库编号').imgUrl(baseURL + 'warehouse_code.png');// 可通过font标签 设置caption颜色
var button1 = toolbar.addImageBoolean(dataObj, 'temperature').caption('<font color="red">温度检测</font>').imgUrl(baseURL + 'temperature.png');
var button2 = toolbar.addImageBoolean(dataObj, 'humidity').caption('湿度检测').imgUrl(baseURL + 'humidity.png');
var button3 = toolbar.addImageBoolean(dataObj, 'statistics').caption('能耗统计').imgUrl(baseURL + 'statistics.png');
var button4 = toolbar.addImageBoolean(dataObj, 'status').caption('保粮状态').imgUrl(baseURL + 'cereals_reserves.png');
var button5 = toolbar.addImageBoolean(dataObj, 'cerealsReserve').caption('视屏监控').imgUrl(baseURL + 'video.png');
注意事项
图片按钮面板可设置两种标题的展示方式,通过 captionPos 进行设置,默认显示在图片下方,也可以设置成鼠标滑入效果。
Tab 面板
var panel = THING.widget.Panel({
template: "default",
hasTitle: true,
titleText: "粮仓信息",
closeIcon: true,
dragable: true,
retractable: true,
width: "380px"
});
var dataObj = {
'基本信息信息': {
'品种': "小麦",
'库存数量': "6100",
'保管员': "张三",
'入库时间': "19:02",
'用电量': "100",
'单仓核算': "无"
},
'粮情信息': {
'仓房温度': "26",
'粮食温度': "22"
},
'报警信息': {
'温度': "22",
'火灾': "无",
'虫害': "无"
},
};
panel.addTab(dataObj);
tab 面板效果图:
进度条
var panel = new THING.widget.Panel({
titleText: "数值型进度条",
width: '400px',
hasTitle: true
});
var dataObj = {
'气温': 0,
'人口数量': 40,
'人口比例': 40,
'progress': 2,
};
panel.position = [10, 10];
panel.addNumberSlider(dataObj, '气温').step(1).min(-20).max(40).isChangeValue(true).on('change', function (value) {
console.log('气温 ' + value);
});
panel.addNumberSlider(dataObj, '人口数量').step(1).min(0).max(123).isChangeValue(true);
panel.addNumberSlider(dataObj, '人口比例').step(1).min(0).max(123).isChangeValue(true).isPercentage(true);
进度条效果图:
创建通栏
// 创建一个左侧通栏
var banner_left = THING.widget.Banner({
column: 'left' // 通栏类型: top 为上通栏(默认), left 为左通栏
});
var baseURL = "https://www.thingjs.com/static/images/sliohouse/"; // 引入图片文件
// 数据对象 为通栏中的按钮绑定数据用
var dataObj = {
orientation: false,
cerealsReserve: false,
video: true,
cloud: true
};
// 向左侧通栏中添加按钮
var img5 = banner_left.addImageBoolean(dataObj, 'orientation').caption('人车定位').imgUrl(baseURL + 'orientation.png');
var img6 = banner_left.addImageBoolean(dataObj, 'cerealsReserve').caption('粮食储存').imgUrl(baseURL + 'cereals_reserves.png');
var img7 = banner_left.addImageBoolean(dataObj, 'video').caption('视屏监控').imgUrl(baseURL + 'video.png');
var img8 = banner_left.addImageBoolean(dataObj, 'cloud').caption('温度云图').imgUrl(baseURL + 'cloud.png');;
通栏效果图:
应用示例
同域 iframe 通信
对于从 ThingJS 网站中上传的页面,与 ThingJS 在线开发环境属于同域。
对于这种情况,在 ThingJS 开发环境中,得到引用的 iframe Dom 节点后,直接通过 contentWindow 获取并调用相应子页面内的全局函数即可,比如:
// 调用同域的iframe页面内的 changeText 方法
iframeDom.contentWindow.changeText('from ThingJS');
相应地,在 iframe 子页面中也可以调用 ThingJS 在线开发页面中的函数方法,比如:
// iframe页面中通过 window.parent 获取并调用 ThingJS 在线开发环境中的函数方法
window.parent.changeLevel('car01');
上述示例的 iframe 页面代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<p id="p1" style='color:white'>Hello World!</p>
<button onclick='callFunctionInMain()'>Into Level</button>
<script>
function callFunctionInMain(){
var name=document.getElementById("p1").innerHTML;
// 调用 ThingJS 在线开发中的函数方法
window.parent.changeLevel(name);
}
// ThingJS 中调用此方法
function changeText(value){
document.getElementById("p1").innerHTML = value;
}
</script>
</body>
</html>
跨域 iframe 通信
对于跨域的 iframe 无法直接相互调用函数。可以利用 HTML5 提供的 postMessage 接口实现跨域 iframe 页面间的相互函数方法调用。
ThingJS 向 iframe 引用的子页面 (https://localhost:3000/pages/index.html) 发送消息。
iframeDom.contentWindow.postMessage
在 iframe 页面中,通过 message 事件,监听 ThingJS 页面发送的消息。
window.addEventListener('message', function(event) {
console.log(event.data);
上述示例的 iframe 页面代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<p id="p1" style='color:white'>Hello World!</p>
<button onclick='callFunctionInMain()'>Into Level</button>
<script>
function callFunctionInMain(){
var name=document.getElementById("p1").innerHTML;
var message = {
'funcName': 'changeLevel',// 所要调用父页面里的函数名
'param': {
'name': name
}
}
//向父窗体发送消息
//第一个参数是具体的信息内容,
//第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送
window.parent.postMessage(message, 'https://www.thingjs.com');
}
function changeText(value){
document.getElementById("p1").innerHTML = value;
}
// 监听从父窗体传来的消息
window.addEventListener('message', function (e) {
var data=e.data;// e.data为传递过来的数据
var funcName=data.funcName;// 需要调用的函数名
var param=data.param;
window[funcName](param.name);
console.log(e.origin); //e.origin 为调用 postMessage 时消息发送方窗口的 origin(域名、协议和端口)
console.log(e.source); //e.source 为对发送消息的窗口对象的引用,可以使用此来在具有不同origin的两个窗口之间建立双向通信
})
</script>
</body>
</html>
ThingJS 引用 Echarts
加载 echarts.js,通过 THING.Utils.dynamicLoad 方法加载 js。
THING.Utils.dynamicLoad(['https://cdn.bootcss.com/echarts/4.1.0.rc2/echarts.js'],function() {})
创建需要的 DOM 节点,DOM 节点为需要在场景中显示的节点。 通过 document.createElement 创建新的 DOM 节点。
//背景颜色 var bottomBackground = document.createElement('div'); //标题 var bottomFont = document.createElement('div'); //图表 var bottomDom = document.createElement('div'); //背景样式右下角对齐 var backgroundStyle = 'bottom:0px; position: absolute;right:0px;height:400px;width:600px;background: rgba(41,57,75,0.74);'; //字体样式 var fontStyle = 'position: absolute;top:0px;right:0px;color:rgba(113,252,244,1);height:78px;width:600px;line-height: 45px;text-align: center;top: 20px;'; //图表DIV样式 var chartsStyle = 'position: absolute;top:80px;right:0px;width:600px;height:300px;'; //设置样式 bottomBackground.setAttribute('style', backgroundStyle); bottomFont.setAttribute('style', fontStyle); bottomDom.setAttribute('style', chartsStyle); //底部标题文字 bottomFont.innerHTML = '温度降水量平均变化图';
初始化 Echarts。加载 echarts.js 完成以后,已经将 Echarts 引入到场景中了。通过以下两步可以得到图表实例:
- 调用 window.echarts 获取 Echarts;
通过 init 方法创建图表实例,传入的参数为需要 Echarts 图表的 DOM 节点,返回的是图表实例;
let bottomCharts = window.echarts.init(bottomDom)
配置图表的属性 图表的各项属性 options 代表的含义可以在 Echarts 官网中查询 。
调用 setOptions 方法将配置好的 options 传入图表
let echartOptions = { "tooltip": { "trigger": "axis", "axisPointer": { "type": "cross", "crossStyle": { "color": "#999" } } }, "legend": { "textStyle": { "color": "auto" }, "data": [ "蒸发量", "降水量", "平均温度" ] }, "xAxis": [ { "axisLabel": { "textStyle": { "color": "#fff" } }, "type": "category", "data": [ "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月" ], "axisPointer": { "type": "shadow" } } ], "yAxis": [ { "type": "value", "name": "水量", "min": 0, "max": 250, "interval": 50, "splitLine": { "lineStyle": { "type": "dotted" }, "show": true }, "nameTextStyle": { "color": "#fff" }, "axisLabel": { "textStyle": { "color": "#fff" }, "formatter": "{value} ml" } }, { "splitLine": { "lineStyle": { "type": "dotted" }, "show": true }, "type": "value", "name": "温度", "min": 0, "max": 25, "interval": 5, "nameTextStyle": { "color": "#fff" }, "axisLabel": { "textStyle": { "color": "#fff" }, "formatter": "{value} °C" } } ], "series": [ { "name": "蒸发量", "type": "bar", "data": [ 2, 4.9, 7, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20, 6.4, 3.3 ] }, { "name": "降水量", "type": "bar", "data": [ 2.6, 5.9, 9, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6, 2.3 ] }, { "name": "平均温度", "type": "line", "yAxisIndex": 1, "data": [ 2, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23, 16.5, 12, 6.2 ] } ], "color": [ "#2b908f", "#90ee7e", "#f45b5b", "#7798BF", "#aaeeee", "#ff0066", "#eeaaee", "#55BF3B", "#DF5353", "#7798BF", "#aaeeee" ] } bottomCharts.setOption(echartOptions);
注意事项
最好在 Echarts 官网或者 ChartBuilder 官网上,将图表的 options 配置完毕,这样可以快速查看配置的效果。
将节点放到 app dom 下。
bottomBackground.appendChild(bottomFont); bottomBackground.appendChild(bottomDom); app.domElement.appendChild(bottomBackground);
常用属性
下表中列出的是面板的常用属性。
属性名称 | 类型 | 说明 |
---|---|---|
position | Array | 设置界面位置,可填写 像素值 或 百分比。 |
positionOrigin | String | 位置原点, 以面板自身的四个顶点为基准点进行偏移 左上角 —— TL/topleft,右上角 ——TR/topright,左下角 —— BL/bottomleft,右下角 —— BR/bottomright。 |