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内置了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);
    

常用属性

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

属性名称 类型 说明
position Array 设置界面位置,可填写 像素值 或 百分比。
positionOrigin String 位置原点, 以面板自身的四个顶点为基准点进行偏移 左上角 —— TL/topleft,右上角 ——TR/topright,左下角 —— BL/bottomleft,右下角 —— BR/bottomright。
该文件修订时间: 2021-06-08 18:22:11

results matching ""

    No results matching ""