当前位置: 首页 > 文档资料 > ThingJS 文档中心 >

2.3.11 2D/3D界面

优质
小牛编辑
171浏览
2023-12-01

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属性。

属性名称类型说明备注
typeString通知系统创建 Marker 物体
offsetArray设置自身坐标系下偏移量为[0, 2, 0]与 localPosition 二选一
localPositionArray设置自身坐标系下偏移量为[0, 2, 0]与 offset 二选一
sizeArray设置 Marker 物体大小,也可以添单独数字如 4,等同于[4,4],大小是以米计算的
urlString图片的 url
parentObject指定 Marker 的父物体
keepSizeBoolean控制是否受距离远近影响,呈现近大远小的 3D 效果。如果设置 true,表示保持大小,不随距离近大远小,此时 size 的单位是屏幕的像素点optional
canvasObject接收 canvas 作为贴图显示optional

下表中列出的是创建 WebView 属性。

属性名称类型说明
typeString通知系统创建 WebView 物体
parentObject指定 WebView 的父物体
urlString图片的 url
widthString3D 中实际宽度 单位 米
heightString3D 中实际高度 单位 米
domWidthString页面宽度 单位 px
domHeightString页面高度 单位 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内置了div2ddiv3d元素,创建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

  1. 加载 echarts.js,通过 THING.Utils.dynamicLoad 方法加载 js。

     THING.Utils.dynamicLoad(['https://cdn.bootcss.com/echarts/4.1.0.rc2/echarts.js'],function() {})
    
  2. 创建需要的 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 = '温度降水量平均变化图';
    
  3. 初始化 Echarts。加载 echarts.js 完成以后,已经将 Echarts 引入到场景中了。通过以下两步可以得到图表实例:

    • 调用 window.echarts 获取 Echarts;
    • 通过 init 方法创建图表实例,传入的参数为需要 Echarts 图表的 DOM 节点,返回的是图表实例;

      let bottomCharts = window.echarts.init(bottomDom)
      
  4. 配置图表的属性 图表的各项属性 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 配置完毕,这样可以快速查看配置的效果。

  5. 将节点放到 app dom 下。

     bottomBackground.appendChild(bottomFont);
     bottomBackground.appendChild(bottomDom);
     app.domElement.appendChild(bottomBackground);
    

常用属性

下表中列出的是面板的常用属性。

属性名称类型说明
positionArray设置界面位置,可填写 像素值 或 百分比。
positionOriginString位置原点, 以面板自身的四个顶点为基准点进行偏移 左上角 —— TL/topleft,右上角 ——TR/topright,左下角 —— BL/bottomleft,右下角 —— BR/bottomright。