基于 HTML5 Canvas 的 3D 模型贴图问题
之前注意到的一个例子,但是一直没有沉下心来看这个例子到底有什么优点,总觉得就是一个 list 列表,也不知道右边的 3d 场景放两个节点是要干嘛,今天突然想起来就仔细地看了一下这个例子的代码,实际操作中应该还是有用处的,就跟大家分享一下。
本例地址: http://hightopo.com/guide/guide/core/listview/examples/example_custom.html
实现图如下,看起来略有点简陋,但是可以自己天马心空增加或者更改成你需要的东西:

首先,创建场景,HT 中有一个 BorderPane 面板组件是拿来页面排布的,可以排布 html 标签,也可以排布 HT 的组件,这里我们将整个页面分为三个部分,顶部工具条 toolbar、左侧列表 listView 和中间 3d 场景 g3d,再将这个面板组件添加进 html body 体中:
borderPane = new ht.widget.BorderPane();//面板组件 toolbar = new ht.widget.Toolbar(); //工具条 listView = new ht.widget.ListView(); //列表组件 g3d = new ht.graph3d.Graph3dView();// 3d 组件 borderPane.setTopView(toolbar);//将 toolbar 放置到面板中的顶部 borderPane.setLeftView(listView, 350); //将 listView 放置到面板中的左侧 borderPane.setCenterView(g3d); //将 g3d 放置到面板中的中间 borderPane.addToDOM(); //将面板组件添加进 body 中
addToDOM 函数是 HT 封装好的将 HT 组件添加进 body 体中的一个方法,其实现逻辑如下:
addToDOM = function(){
var self = this,
view = self.getView(),//通过 getView 函数获取组件的底层 div
style = view.style;
document.body.appendChild(view); //body 添加孩子 view
style.left = '0';
style.right = '0';
style.top = '0';
style.bottom = '0';
window.addEventListener('resize', function () { self.iv(); }, false);//窗口大小变化时,立即刷新组件
}
我们一个部分一个部分来解析,从最上层的 toolbar 工具条开始,如下:

工具条也是分为三个部分,一是左侧的搜索框,二是中间的分割线,三是右侧的点击按钮。
我们首先向工具条 toolbar 中添加这三个元素,具体添加方法请参考 HT for Web 工具条手册:
toolbar.setItems([//设置工具条元素数组
{
id: 'text',
label: 'Search',
icon: 'images/search.png',
textField: {
width: 120
}
},
'separator',
{
label: 'Sort by price',
type: 'toggle',//toggle表示开关按钮
selected: true,
action: function(){
listView.setSortFunc(this.selected ? sortFunc : null);
}
}
]);
接下来向左侧的 listView 列表中添加数据,这个数据就是 product.js 中的变量 products,通过遍历这个数组变量,将这个数组中的所有值都填充到 listView 列表中:

products.forEach(function(product){//products 是在product.js 文件中定义的
var data = new ht.Data();
data.a(product);//设置数据 data 的 attr 属性
、listView.dm().add(data);//将 data 添加进 listView 的数据容器中
});
然后对 listView 列表进行一系列的样式属性的设置:行高、背景、icon 图标、文字提示等等。代码如下,解释都在代码中了,还有不懂的请查阅 HT for Web 列表手册:
listView.setRowHeight(50);//设置行高
listView.drawRowBackground = function(g, data, selected, x, y, width, height){//绘制行背景色,默认仅在选中该行时填充选中背景色,可重载自定义
if(this.isSelected(data)){//选中时
g.fillStyle = '#87A6CB';
}
else if(this.getRowIndex(data) % 2 === 0){//偶数行时
g.fillStyle = '#F1F4F7';
}
else{
g.fillStyle = '#FAFAFA';
}
g.beginPath();
g.rect(x, y, width, height);
g.fill();
};
// HT 通过 ht.Default.setImage('name', json) 函数来注册图片
ht.Default.setImage('productIcon', {
width: 50,
height: 50,
clip: function(g, width, height) {//clip 用于裁剪绘制区域
//利用canvas画笔绘制,实现自定义裁剪任意形状的效果
//这里是将图片裁剪成圆形
g.beginPath();
//x, y, radius, startAngle, endAngle, anticlockwise
g.arc(width/2, height/2, Math.min(width, height)/2-3, 0, Math.PI * 2, true);
g.clip();
},
comps: [//矢量图形的组件Array数组,每个数组对象为一个独立的组件类型,数组的顺序为组件绘制先后顺序
{
type: 'image',
stretch: 'uniform',//图片始终保持原始宽高比例不变化,并尽量填充满矩形区域
rect: [0, 0, 50, 50],//指定组件绘制在矢量中的矩形边界 [x, y, width, height]四个参数方式,分别代表左上角坐标x和y,以及宽高width和height
name: {func: function(data){return data.a('ProductId');}}//图片的名字为 data.a('ProductId') 返回的值
}
]
});
listView.setIndent(60);//设置indent缩进,该参数一般用于指定图标的宽度
listView.getIcon = function(data){//返回data对象对应的icon图标,可重载自定义
return 'productIcon';//这个是前面 ht.Default.setImage 函数注册过的矢量图形
};
listView.enableToolTip();//开启文字提示
listView.getLabel = function(data){//返回data对象显示的文字,默认返回data.toLabel(),可重载自定义
return data.a('ProductName') + ' - $' + data.a('UnitPrice').toFixed(2);
};
listView.getToolTip = function(e){//根据传入的交互事件,返回文本提示信息,可重载自定义
var data = this.getDataAt(e);//传入逻辑坐标点或者交互event事件参数,返回当前点下的数据元素
if(data){
return '<span style="color:#3D97D0">ProductId: </span>' + data.a('ProductId') + '<br>' +
'<span style="color:#3D97D0">ProductName: </span>' + data.a('ProductName') + '<br>' +
'<span style="color:#3D97D0">QuantityPerUnit: </span>' + data.a('QuantityPerUnit') + '<br>' +
'<span style="color:#3D97D0">Description: </span>' + data.a('Description');
}
return null;
};
列表组件中还封装了一个很方便的函数 setSortFunc,用于设置排序函数,用户也可以自定义,目前我们希望对这些”商品“进行排序:
sortFunc = function(d1, d2){//自定义排序函数
return d1.a('UnitPrice') - d2.a('UnitPrice');
};
listView.setSortFunc(sortFunc);//HT 定义的 设置排序函数
因为我们要进行数据的搜索,就要对数据以及显示方面进行过滤,因为在数据变化时,HT 无法获知需要更新,这时候就要我们手动对有显示变化的部分调用更新函数 invalidate 简写为 iv。
我们对文本输入框的键盘弹起事件进行事件的监听,然后判断我们输入的值在 listView 列表中是否存在等操作对显示界面进行过滤:
// 对text文本框进行键盘按键弹起事件监听 toolbar.getItemById('text').element.getElement().onkeyup = function(e){
listView.invalidateModel();//无效模型,最彻底的刷新方式 “完全刷新”
};
//如果文本框输入的值在
listView.setVisibleFunc(function(data){//设置可见过滤器
var text = toolbar.v('text');//getValue(id)根据id获取对应item元素值,简写函数为v(id)
if(text){
return data.a('ProductName').toLowerCase().indexOf(text.toLowerCase()) >= 0;//indexOf()方法返回在类型数组中可以找到给定元素的第一个索引,如果不存在,则返回-1
}
return true;
});
第三个部分,右侧 3d 场景,利用的是 HT 的三维组件 ht.graph3d.Graph3dView,然后在 3d 场景上添加两个节点,作为对照:
//创建两个节点放到 3d 场景中
var node = new ht.Node();
node.s3(30, 30, 30);//设置三维大小
node.p3(-30, 15, 0);//设置三维坐标
node.s('all.color', '#87A6CB');//设置 node 的六个面颜色
g3d.dm().add(node);//将新建的 node 添加进 3d 场景的数据容器中
var node = new ht.Node();
node.s3(30, 30, 30);
node.p3(30, 15, 0);
node.s('all.color', '#87A6CB');
node.setElevation(15);
g3d.dm().add(node);
g3d.setEye(-100, 100, 80);//设置 3d 场景的眼睛(或Camera)所在位置,默认值为[0, 300, 1000]
g3d.setGridVisible(true);//设置是否显示网格
g3d.setGridColor('#F1F4F7');//设置网格线颜色
整个场景创建完毕,接下来就是将 listView 中显示的 icon 图标拖拽到 3d 中的节点上,作为贴图。列表组件中封装了一个拖拽的功能 handleDragAndDrop,这个函数有两个参数,event 交互事件和 state 当前状态,我们对拖拽事件的不同状态进行不同的处理:
listView.handleDragAndDrop = function(e, state){//该函数默认为空,若该函数被重载,则pan平移组件功能将被关闭
if(state === 'prepare'){//state当前状态,先后会有prepare-begin-between-end四种过程
var data = listView.getDataAt(e);//传入逻辑坐标点或者交互event事件参数,返回当前点下的数据元素
listView.sm().ss(data);//设置选中当前事件所在的数据元素
if(dragImage && dragImage.parentNode){
document.body.removeChild(dragImage);
}
dragImage = ht.Default.toCanvas('productIcon', 30, 30, 'uniform', data);
// toCanvas(image, width, height, stretch, data, view, color)将图片转换成Canvas对象
productId = data.a('ProductId');
}
else if(state === 'begin'){
if(dragImage){
var pagePoint = ht.Default.getPagePoint(e);//返回page属性坐标
dragImage.style.left = pagePoint.x - dragImage.width/2 + 'px';//实时更新拖拽时的图标的位置
dragImage.style.top = pagePoint.y - dragImage.height/2 + 'px';
document.body.appendChild(dragImage);//在 html body 体中添加这个拖拽的图片
}
}
else if(state === 'between'){
if(dragImage){
var pagePoint = ht.Default.getPagePoint(e);//返回page属性坐标
dragImage.style.left = pagePoint.x - dragImage.width/2 + 'px';
dragImage.style.top = pagePoint.y - dragImage.height/2 + 'px';
if(ht.Default.containedInView(e, g3d)){//判断交互事件所处位置是否在View组件之上,一般用于Drog And Drop的拖拽操作判断
//这边做了两个判断,一个是鼠标在拖拽的时候未松开,一个是鼠标拖拽的时候松开了。
if(lastFaceInfo){//鼠标未松开的情况下,贴图显示旧值
//data.face 默认值为front,图标在3D下的朝向,可取值left|right|top|bottom|front|back|center
lastFaceInfo.data.s(lastFaceInfo.face + '.image', lastFaceInfo.oldValue);
lastFaceInfo = null;
}
//鼠标松开时,将新值赋给这个面
var faceInfo = g3d.getHitFaceInfo(e);//获取鼠标所在面信息
if(faceInfo){
faceInfo.oldValue = faceInfo.data.s(faceInfo.face + '.image');//获取面的“老值”
faceInfo.data.s(faceInfo.face + '.image', productId);//front/back/top/bottom/left/right.image 设置这些面的贴图
lastFaceInfo = faceInfo;
}
}
}
}
else{//拖拽结束之后,所有值都回到初始值
if(dragImage){//有从列表中拖拽图片
if(lastFaceInfo){//有赋“图片”到 3d 中的节点上
lastFaceInfo.data.s(lastFaceInfo.face + '.image', lastFaceInfo.oldValue);
lastFaceInfo = null;
}
if(ht.Default.containedInView(e, g3d)){
var faceInfo = g3d.getHitFaceInfo(e);
if(faceInfo){
faceInfo.data.s(faceInfo.face + '.image', productId);
}
}
if(dragImage.parentNode){
document.body.removeChild(dragImage);
}
dragImage = null;
productId = null;
}
}
};
基于 HTML5 Canvas 的 3D 模型贴图问题的更多相关文章
- 基于 HTML5 Canvas 的 3D 模型列表贴图
少量图片对于我们赋值是没有什么难度,但是如果图片的量大的话,我们肯定希望能很直接地显示在界面上供我们使用,再就是排放的位置等等,这些都需要比较直观的操作,在实际应用中会让我们省很多力以及时间.下面这个 ...
- 基于 HTML5 Canvas 的 3D 机房创建
对于 3D 机房来说,监控已经不是什么难事,不同的人有不同的做法,今天试着用 HT 写了一个基于 HTML5 的机房,发现果然 HT 简单好用.本例是将灯光.雾化以及 eye 的最大最小距离等等功能在 ...
- 基于 HTML5 Canvas 的 3D WebGL 机房创建
对于 3D 机房来说,监控已经不是什么难事,不同的人有不同的做法,今天试着用 HT 写了一个基于 HTML5 的机房,发现果然 HT 简单好用.本例是将灯光.雾化以及 eye 的最大最小距离等等功能在 ...
- 基于 HTML5 Canvas 的 3D 渲染引擎构建机架式服务器
前言 今天找到了 HT 的官网里的 Demo 网站( http://www.hightopo.com/demos/index.html ),看的我眼花缭乱,目不暇接. 而且 HT 的用户手册,将例子和 ...
- 基于 HTML5 Canvas 的 3D 压力器反序列化
在实际应用中,我觉得能够通过操作 JSON 文件来操作 3D 上的场景变化是非常方便的一件事,尤其是在做编辑器进行拖拽图元并且在图元上产生的一系列变化的时候,都能将数据很直观地反应给我们,这边我们简单 ...
- 基于 HTML5 Canvas 的 3D 渲染引擎构建生产管控系统
前言 大家好,老郑我又回来了.这一期为大家带来一个非常好玩的 demo,我们制作一套自己的 3D 管道控制系统,运用了( http://www.hightopo.com )HT 的 Graph3dVi ...
- 基于HTML5 Canvas的3D动态Chart图表
发现现在工业SCADA上或者电信网管方面用图表的特别多,虽然绝大部分人在图表制作方面用的是echarts,他确实好用,但是有些时候我们不能调用别的插件,这个时候就得自己写这些美丽的图表了,然而图表轻易 ...
- 基于 HTML5 Canvas 的 3D 碰撞检测
这是公司大神写的一个放官网上给用户学习的例子,我一开始真的不知道这是在干嘛,就只是将三个形状图元组合在一起,然后可以同时旋转.放大缩小这个三个图形,点击"Animate"就能让中间 ...
- 基于 HTML5 Canvas 的 3D 热力云图效果
前言 数据蕴藏价值,但数据的价值需要用 IT 技术去发现.探索,可视化可以帮助人更好的去分析数据,信息的质量很大程度上依赖于其呈现方式.在数据分析上,热力图无疑是一种很好的方式.在很多行业中都有着广泛 ...
随机推荐
- [JAVA第二课] java命名规则
Java良好的命名规则以及代码风格可以看出来一个程序员的功底,好多公司也会注重这方面,他们招聘员工在有些时候往往就是根据一个人的代码风格来招人,所以下面就就我知道的代码风格作简要的说明一下.Java命 ...
- [hihoCoder]无间道之并查集
题目大意: #1066 : 无间道之并查集 时间限制:20000ms 单点时限:1000ms 内存限制:256MB 描述 这天天气晴朗.阳光明媚.鸟语花香,空气中弥漫着春天的气息……额,说远了,总之, ...
- Web前端性能优化——如何有效提升静态文件的加载速度
WeTest 导读 此文总结了笔者在Web静态资源方面的一些优化经验. 一.如何优化 用户在访问网页时, 最直观的感受就是页面内容出来的速度,我们要做的优化工作, 也主要是为了这个目标.那么为了提高页 ...
- Appium python自动化测试系列之等待函数如何进行实战(九)
9.1 等待函数的使用 9.1.1 为什么要使用等待函数 我们在做自动化的时候很多时候都不是很顺利,不是因为app的问题,我们的脚本也没问题,但是很多时候都会报错,比如一个页面本来就有id为1的这个 ...
- OpenCV4Android背景建模(MOG、MOG2)
本文为作者原创,转载请注明出处(http://www.cnblogs.com/mar-q/)by 负赑屃 很久以前的笔记了,分享给大家吧...OpenCV4Android中用于背景建模的类主要 ...
- 【深度学习系列】手写数字识别卷积神经--卷积神经网络CNN原理详解(一)
上篇文章我们给出了用paddlepaddle来做手写数字识别的示例,并对网络结构进行到了调整,提高了识别的精度.有的同学表示不是很理解原理,为什么传统的机器学习算法,简单的神经网络(如多层感知机)都可 ...
- 如何线上部署node.js项目
来源:http://blog.csdn.net/chenlinIT/article/details/73343793 前言 最近工作不是很忙,在空闲时间学习用node+express搭建自己的个人博客 ...
- ES6新特性之Symbol使用细节
在迭代器章节的时候出现过[Symbol.iterator ]的属性,那么到底Symbo到底是什么? 答:Symbol是ES6新定义的一种值,它既不是字符串,也不是对象,而是为javaScript增加的 ...
- Phalanx
Phalanx Time Limit:5000MS Memory Limit:32768KB 64bit IO Format:%I64d & %I64u Submit Stat ...
- The Worm Turns
The Worm Turns Time Limit: 8000/4000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Tota ...