什么是这里所说的360全景h5页面?查看下面的案例进行了解:

开发项目:http://game.flyh5.cn/resources/game/wechat/zjh/fangtuo/index.html
案例1:http://cpic18ny1.energytrust.com.cn/
案例2:https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxc8a2ce35fa3799ea&redirect_uri=https%3A%2F%2Fapi-wx.51h5.com%2Fweb%2Foauth%2Fcallback%2Fid%2Fzhihuipingmu.html%3Fredirect%3Dhttp%253A%252F%252Fevent.ews.m.jaeapp.com%252Foneleaf%252Fwx%252F&response_type=code&scope=snsapi_base&connect_redirect=1&state=af817cf4f82372fbb17733029aebd16e#wechat_redirect
案例3:http://wap.i-h5.cn/hzx_game/dell/index.html?from=groupmessage&isappinstalled=0

我所做的项目是一个展示类的h5营销项目,没有太多的交互逻辑,主要在于那个360场景的编写,其实我也是赶鸭子上架,boos突然丢了一个案例给我研究一下,说会有一个项目进来要做成360旋转效果,我&*¥@%*@¥%@@—¥#¥。

好吧,研究的坎坷历程就略过了吧,我也是在别人的项目基础上做的文章,而且之前也没有写过3d动画,所以只拿一些我研究出来的东西说道记录一下,具体原理我也一知半解,不过使用他写个简单项目应该还是够了。

项目中主要使用的3d动画库为:css3d-engine.js。github为:https://github.com/shrekshrek/css3d-engine
还使用了一个名为jstween的动画库,可以配合css3d-engine.js使用。代码中以JT代替。github为:https://github.com/shrekshrek/jstween

项目过程中使用了PxLoader进行360场景图片等资源的预加载。github为:https://github.com/thinkpixellab/PxLoader

话不多说,让我们一步一步来构建一个简单的360场景动画吧。

C3D.Stage

三维场景,需要首先创建,其他所有显示内容都通过addchild方法放入场景即可。

const anta = new C3D.Stage({el: $("#anta")[0]
});

我们选用一个id为anta的一个div作为场景的承载元素。

C3D.Sprite

C3D.Sprite是一个三维显示元素基类,一般作为容器使用,用它来创建一个显示在页面上的元素的容器

const spMain = new C3D.Sprite();
spMain.position(0, 0, -750).update();
anta.addChild(spMain);

创建一个C3D.Sprite容器作为承载所有显示内容的主容器,后续所有的显示内容都添加到这个容器中。并添加这个主容器到场景中。

C3D.Plane

C3D.Plane表示为一个平面,就是一个二维平面,一般用它来创建一个显示元素,也就是场景中所有可以看到的平面都是用它来创建,比如场景中交互的按钮这些,通过创建一个C3D.Plane,并把它添加到C3D.Sprite容器中即可。

ps:刷新相应的dom内容,位置,角度,尺寸,材质等信息只有在执行了update方法后才会被作用到dom节点,具体可以参考github

创建场景背景

场景的背景也是通过一个C3D.Sprite容器来承载,并且显示的背景元素会通过C3D.Plane来创建,这里会配置一些参数,具体看代码:

    // 一些参数定义// 定义背景图片的个数var bg_num = 20;// 背景的宽高信息,也就是那张背景大图的宽高 bgInfovar o = {w: 3868,h: 2474},// 每一个背景元素的宽度。其实就是背景大图的宽度除以你的切好的背景图片个数M = o.w / 20,// 此处需要根据不同的场景宽度(大图的总宽度)进行调整,否则背景图片元素之间可能有间隔h = 605,//bgImage = [// 这里是场景的背景图片url链接,比如:'bg-image1.jpg','bg-image2.jpg',...];// 创建背景容器var panoBg = new C3D.Sprite();// No need for attentionvar d = {lat: 0,lon: 0},f = {lon: 0,lat: 0};// c.lon设置场景的水平初始的位置,也就是场景生成后,默认停留到哪一个位置,这里是角度// 你可以根据项目自行设置场景最后停留的位置。而且在场景初始动画中,他的动画结束位置也需要// 和此处保持一致var c = {lon: 205,lat: 0,};var p = true;// 设置背景容器初始信息,并添加至spMain这个主容器中。panoBg.name("panoBg").position(0, 0, 0).update();spMain.addChild(panoBg);for (var R = 0; R < bg_num; R++) {// No need for attentionvar F = new C3D.Plane,H = -360 / bg_num * R,J = H / 180 * Math.PI,U = h;// 设置场景的基本显示信息,这里默认是不可见的,alpha为0// size:设置大小 position:设置位置 rotation:设置选择角度 visibility:设置可见性// material 设置材质,这里的话,直接用来简单的设置背景图片F.size(M, o.h).position(Math.sin(J) * U, 150, Math.cos(J) * U).rotation(0, H + 180, 0).visibility({alpha: 0}).material({image: bgImage.url,bothsides: !1}).update();panoBg.addChild(F);}// 重力感应,通过监测重力感应,做到场景随手机的摇摆进行改动。// 通过此重力感应更新d,f,c这几个对象的信息,这几个对象具体有什么用,// 我也不太清楚。但是有些不支持这个Orienter的可能就不行。var o2 = new Orienter();o2.handler = function (t) {d.lon = -t.lon;d.lat = t.lat;if (p) {f.lat = -d.lat;f.lon = -d.lon;}};o2.init();

场景的背景是以一张左右可以闭合的大图,垂直等分为一定数量的等宽小图片,比如一张大图垂直等分为20张小图,每张小图都创建一个C3D.Plane元素。

ps: 代码中基本上我知道的都注释了,不过代码中注释了 No need for attention 字样的,表示我也不是太清楚具体的作用,但这些不用了解也可以基本满足功能。就这样就可以了。

至此,360场景中的背景的实现基本上就完成了。不过现在还是不可见的状态,不过先不急,稍后会讲解。我们先来往场景中添加一些可交互的按钮吧。

增加交互按钮(图片按钮)

360场景中的交互,一般就是可点击的按钮,这是最基础的交互方式,这里也是通过创建一个C3D.Sprite主容器用来承载所有的交互区域,并且一个交互区域也是通过一个C3D.Plane来创建的,具体看代码:

    var btnDots = [// 通过这个配置对象,生成场景中的交互元素。ps:这一类的交互一定是类似的// 如果不是,可以根据这个自己重新编写其他的交互元素// name: 这个交互元素的名称,不要重名了 x,y:此元素在场景中的位置// dot为所需要的背景图片地址,w,h为此元素的宽高。{name: "dialog1",x: 667,y: 1068,dot: 'images/click.png',w: 186,h: 196,},{name: "dialog2",x: 1960,y: 1400,dot: 'images/click.png',w: 186,h: 196,},]var panoDots = new C3D.Sprite;panoDots.name("panoDots").visibility({alpha: 0}).position(0, 0, 0).update();$.each(btnDots, function (A, B) {// No need for attentionvar g = B,Q = -360 * (g.x - 80) / o.w,G = 90 * (g.y - o.h / 2) / o.h,M = Q / 180 * Math.PI,Y = h - 80,// C3D.create 通过配置来创建指定的元素i = C3D.create({type: "sprite",name: g.name,scale: [1],children: [{type: "plane",name: "dot",size: [g.w, g.h],position: [0, 2, 2],rotation: [G, 0, 0],material: [{image: g.dot,size: 'cover',}],bothsides: !1},{type: "plane",name: "label",size: [0, g.h],rotation: [G, 0, 0],origin: [-18, 33],material: [{image: g.label}],bothsides: !1}]});i.position(Math.sin(M) * Y, .9 * (g.y - o.h / 2), Math.cos(M) * Y).rotation(0, Q + 180 - 5, 0).updateT(),// 为此元素绑定触摸事件,此处可以用于点击时,执行一些操作。i.on("touchend", function () {// TODO}),// No need for attentioni.r0 = Q,i.w0 = g.w,i.dot.alpha = .5,i.dot.updateV(),panoDots.addChild(i)});spMain.addChild(panoDots);

以上就是向场景中添加一些交互元素,目前只是实现了简单的点击交互,而且,现在场景中还无法移动,仅仅只能通过Orienter进行移动,所以我们需要给anta这个场景添加触摸事件。来支持场景随手指移动进行移动。

为场景增加触摸事件

    // No need for attentionvar originTouchPos = {x: 0,y: 0},oldTouchPos = {x: 0,y: 0},newTouchPos = {x: 0,y: 0},firstDir = "",originTime = 0,oldTime = 0,newTime = 0,dx = 0,dy = 0,ax = 0,ay = 0,time = 0;// touchstart事件处理函数var onTouchStart = function (t) {firstDir = "",t = t.changedTouches[0];originTouchPos.x = oldTouchPos.x = newTouchPos.x = t.clientX;originTouchPos.y = oldTouchPos.y = newTouchPos.y = t.clientY;originTime = oldTime = newTime = Date.now();dx = dy = ax = ay = 0,anta.on("touchmove", onTouchMove),anta.on("touchend", onTouchEnd)};anta.on("touchstart", onTouchStart);var onTouchMove = function (t) {anta.off("touchend", onTouchEnd);return t = t.changedTouches[0],newTouchPos.x = t.clientX,newTouchPos.y = t.clientY,newTime = Date.now(),checkGesture(),oldTouchPos.x = newTouchPos.x,oldTouchPos.y = newTouchPos.y,oldTime = newTime, !1};var onTouchEnd = function (e) {newTime = Date.now();var t = (newTime - oldTime) / 1e3;// 这里可以通过e.target获取到触发了此touchend事件的dom对象,// 这样就可以根据此对象来判断是点击了哪个场景的元素。当然,可以直接为元素绑定点击事件,在此处绑定// TODOanta.off("touchmove", onTouchMove),anta.off("touchend", onTouchEnd);}// No need for attentionfunction checkGesture() {dx = fixed2(newTouchPos.x - originTouchPos.x),dy = fixed2(newTouchPos.y - originTouchPos.y),ax = fixed2(newTouchPos.x - oldTouchPos.x),ay = fixed2(newTouchPos.y - oldTouchPos.y),time = (newTime - oldTime) / 1e3,"" == firstDir && (Math.abs(ax) > Math.abs(ay) ? firstDir = "x" : Math.abs(ax) < Math.abs(ay) && (firstDir = "y"));// 此处可以调整45和-45的数值,他们用于场景的上下视角控制if (!p) {c.lon = (c.lon - .2 * ax) % 360,c.lat = Math.max(-45, Math.min(45, c.lat + .2 * ay))}}// 功能函数function fixed2(t) {return Math.floor(100 * t) / 100}

上述为场景增加的触摸事件,他并没有更新场景位置的功能,也就是他在手指滑动时,只是更新了一些位置信息数据,而触发场景位置更新的另有其他的函数执行。
而且touchmove事件时为了场景的移动而触发,而touchend是作为场景的点击的功能触发,所以他们的执行
都会移除后续相应的touch事件。以下是持续触发的场景位置更新操作,他是一直在执行的:

    // 执行动画,有兴趣的可以了解一下requestAnimationFramerequestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame ||function (callback) {setTimeout(callback, 1000 / 60);};// 执行场景的位置更新操作,他根据触摸事件更新的数据来进行场景的位置更新。function actiondh() {var t = (d.lon + f.lon + c.lon) % 360,i = .35 * (d.lat + f.lat + c.lat);t - panoBg.rotationY > 180 && (panoBg.rotationY += 360),t - panoBg.rotationY < -180 && (panoBg.rotationY -= 360);var n = t - panoBg.rotationY,a = i - panoBg.rotationX;Math.abs(n) < .1 ? panoBg.rotationY = t : panoBg.rotationY += .3 * n,Math.abs(a) < .1 ? panoBg.rotationX = i : panoBg.rotationX += .15 * a,panoBg.updateT();// 每次更新场景位置时,都需要重新更新你添加的交互元素的位置,你添加了哪些容器// 每次就需要更新那些容器的位置。panoDots.rotationY = panoBg.rotationY,panoDots.rotationX = panoBg.rotationX,panoDots.updateT(),t - panoItems.rotationY > 180 && (panoItems.rotationY += 360),t - panoItems.rotationY < -180 && (panoItems.rotationY -= 360);var o = t - panoItems.rotationY,r = i - panoItems.rotationX;Math.abs(o) < .1 ? panoItems.rotationY = t : panoItems.rotationY += .25 * o, Math.abs(r) < .1 ? panoItems.rotationX = i : panoItems.rotationX += .15 * r, panoItems.updateT();var s12 = -150 - 20 * Math.abs(n);spMain.z += .1 * (s12 - spMain.z),spMain.updateT(),A = requestAnimationFrame(actiondh);}

这样,每次通过重力感应对象Orienter或者通过触摸事件时都会更新场景的位置信息。基本至此,一个360场景h5项目的简单点击交互功能都可以实现了,还有一些有点复杂的交互判断,我自己也不太搞得清楚所以就不拿出来说了。

卷轴般的展开动画效果

基本上我们看这种类型的h5项目都有一个类似卷轴般的展开动画效果,其时机在于场景背景资源加载完毕时,就可以通过JT动画库来实现一个卷轴效果,具体代码如下:

    // loc是和上面创建背景容器时设置的c变量中的lon是一致的。var loc = 205;const stageInit = function stageInit() {// spMain设置主容器的缩放,z为缩放,此处是从小变大的缩放效果,持续四秒,并且每次动画执行都需要更新场景的位置信息。JT.fromTo(spMain, 4, {z: -2200}, {z: -150,ease: JT.Quad.Out,onUpdate: function () {this.target.updateT().updateV()}, onEnd: function () {p = false// 开始执行场景位置更新actiondh();}});// 设置背景容器的卷轴滚动效果。JT.fromTo(panoBg, 4, {rotationY: -720,}, {rotationY: loc,ease: JT.Quad.Out,onUpdate: function () {this.target.updateT().updateV()}, onEnd: function () {this.target.updateT().updateV()}});// 设置背景容器中的所有背景元素的滚动动画(主要效果)for (var A = 0, B = panoBg.children.length; B > A; A++) {JT.from(panoBg.children[A], 0.5, {x: 0,z: 0,scaleX: 0,scaleY: 0,delay: .05 * A,ease: JT.Quad.Out,onUpdate: function () {this.target.updateT()},onStart: function () {this.target.visibility({alpha: 1}).updateV()}});}// 等到卷轴滚动动画执行完后,显示交互元素JT.fromTo(panoDots, 0.1, {rotationY: -360,alpha: 0}, {rotationY: loc,alpha: 1,delay: 4,ease: JT.Quad.Out,onUpdate: function () {this.target.updateT().updateV()},onStart: function () {this.target.visibility({alpha: 1}).updateV()}})}

以上代码就是场景的类似卷轴滚动的交互动画,而此动画的执行时机可以根据项目需要进行调用。比如通常项目会有loading页,可以等待loading页加载完后在执行此动画函数。

至此一个简单的360场景类h5交互项目就可以实现了,不过在此还是有一些需要注意的地方:

1.场景的背景图最好不要太大,因为图像越大,在执行场景动画时效果就卡,ios还好,安卓就不太理想了。
2.场景图最好提前使用PxLoader进行预加载,他的使用也很简单,看一下官方文档即可,你也可以等场景图预加载完成后在执行场景动画函数。PxLoader的github:https://github.com/thinkpixellab/PxLoader
3.注意场景背景创建中的h变量的设置,不同的总图片宽度不同,h变量就不同,需要自行测试设置,图片宽度越大
则h需要调整的越大。

总结:
最开始boos丢个案列给我时,让我以这个案例为基础改出一个新项目时,其实我是一脸懵b的,不过老板交代的,没办法,所幸也不是很复杂的项目,交互也仅仅限于场景中的点击交互,经过我一段段代码的测试,一个个参数的修改,查明他是什么意思,费了比较大的一番功夫,而且有些东西不是全都可以明白的。让我自己从头开始重新写,那可就不是看看别人写的这么简单的了,不过也不是没有收获吧,编写这种360效果的h5项目,让我多了解了一个新领域,而且,相对于功能单一,交互简单的这种,我对一个叫做krpano的软件编写出来的全景效果更感兴趣。而且这也可以作为一个技术方向,了解和学习。

ps:以上代码是在某个项目中截取出来,以作为学习交流之用,如有侵权,请联系删除。

使用css3d-engine.js编写简单的 360全景h5页面相关推荐

  1. android使用WebView实现显示360°全景H5页面

    xml中的控件 <com.tencent.smtt.sdk.WebView//需使用下面的库 android:id="@+id/webView" android:layout ...

  2. 用p5.js编写简单的动态图形——波纹扩散

    用p5.js编写简单的动态图形--波纹扩散 第一次使用p5.js写程序,如有错误请指出,多多指教. 没有下载p5.js的小伙伴可以直接使用网页版的,简单注册一个账号之后就可以保存代码啦. 网站:htt ...

  3. 原生JS编写简单的编辑器

    使用vue编写的,没有任何依赖,可改写其它形式 轻量级编辑器只是在document.execCommand()方法做了包装,不兼容的浏览器器生成的标签是一致的,所以的富文本的选择要根据项目决定 实现了 ...

  4. 使用Pixi.js编写JavaScript网页小游戏

    Pixi.js中文网https://pixijs.huashengweilai.com/PixiJSOfficial site for PixiJS, The HTML Creation Engine ...

  5. 游戏开发:js实现简单的板球游戏

    js实现简单的板球游戏 大家好,本次我们来使用js来实现一个简单的板球游戏.截图如下: 首先,设计页面代码,页面代码很简单,因为整个几乎是使用js编写的,页面几乎没有代码,如下: <!DOCTY ...

  6. 【Windows 逆向】CheatEngine 工具 ( 汉化版 CE 工具推荐 | 编写简单 C++ 程序 | C++ 程序执行分析 | 使用 CE 修改上述 C++ 程序 )

    文章目录 一.汉化版 CE 工具推荐 二.编写简单 C++ 程序 三.C++ 程序执行分析 四.使用 CE 修改上述 C++ 程序 一.汉化版 CE 工具推荐 推荐一个汉化版的 CE 工具 : htt ...

  7. JavaScript基础01【简介、js编写位置、基本语法(6种基本数据类型)】

    学习地址: 谷粒学院---尚硅谷 尚硅谷最新版JavaScript基础全套教程完整版(140集实战教学,JS从入门到精通) JavaScript基础.高级学习笔记汇总表[尚硅谷最新版JavaScrip ...

  8. 好程序员前端分享使用JS开发简单的音乐播放器

    好程序员前端分享使用JS开发简单的音乐播放器,最近,我们在教学生使用JavaScript,今天就带大家开发一款简单的音乐播放器.首先,最终效果如图所示: 首先,我们来编写html界面index.htm ...

  9. node.js编写网页_为Node.js编写可扩展架构

    node.js编写网页 by Zafar Saleem 通过Zafar Saleem 为Node.js编写可扩展架构 (Writing Scalable Architecture For Nodejs ...

最新文章

  1. 5013.FortiGate企业级硬件防火墙Demo演示文档
  2. 图解Numpy的tile函数
  3. 文巾解题 183. 从不订购的客户
  4. 双网卡连接mysql数据库_双网卡实现两台电脑共享上网经验笔记
  5. Github 上 10 个值得学习的 Springboot 开源项目
  6. Spring Rmi配置
  7. 这些数学趣图,数学老师看了后会怎么想?
  8. 3月1日见,魅族新品要来了!
  9. vue 打开html流_【报Bug】“纯nvue”模式下,web-view无法打开本地html
  10. HDU 2460 Network(双连通+树链剖分+线段树)
  11. CMOS checksum error-Defaults loaded 故障解决办法
  12. LINUX第2天——基础操作
  13. MySql常用SQL语句
  14. VC6.0中使用Activex控件小结
  15. python人机对战_人机对战初体验:Python实现四子棋游戏
  16. 使用Python face_recognition 人脸识别 - 12 人脸图片1-N比对
  17. 并发编程 CAS算法
  18. 图像识别的原理、过程、应用前景
  19. 在韩国5G商用神话中,我们不能学到什么?
  20. 文本乱码怎么办?教你一招批量修改文本文件的编码格式,轻松解决乱码问题

热门文章

  1. 虚拟机Linux CentOS 7安装配置Tomcat10(适用于安装任何tomcat版本!!)
  2. 【编码实战】2022年还在用jjwt操作jwt?,推荐你使用nimbus-jose-jwt,爽到飞起~
  3. **长庆油田.长庆局和四川石油管理局合并的通知(ZT)
  4. 中国制冷剂市场供需调研与投资竞争力分析报告2022-2028年
  5. 智能的尴尬--《命名和指称》
  6. 基于PHP网上考试系统,试卷、试题——基于php网上考试系统.doc
  7. weblogic10.3.6升级补丁时间慢的问题
  8. 创新的时机 – 黄金点游戏
  9. 别把激励员工变成收买员工
  10. 应用安全系列之二十三:SSRF