其实是依托Css3的功劳,先上一个例子

链接: https://pan.baidu.com/s/1cZ-mMI01FHO3u793ZhvF2w 提取码: d3s7
代码地址:链接: https://pan.baidu.com/s/1sldhljJ 密码: i6qh

这动画纵有万般变化,也离不开以下几个属性

  • transform (元素2D 3D转换)

    translate,3d,X,Y,Z (移动距离)
    scale,3d,X,Y,Z (缩放比例)
    rotate,3d,X,Y,Z (旋转角度)
    skew,X,Y (倾斜角度)

  • transform-origin (允许被转换元素位置)

    left center right length %

  • transform-style (被嵌套元素在3D空间中显示)

    flat (2d) presever-3d (3d)

  • perspective (3D元素透视效果 俗称"景深")

    number

  • perspective-origin (设置3D基数位置 x,y)

    top center right length %

  • backface-visibility (元素不面对屏幕是否可见)

    visible hidden


这里写一个变化的例子,帮助理解

以上例子只是单一的变化 如果多个变化一起执行 遵守 “慢写的先执行
比如:
原始图片

"translateX(150px) rotateY(180deg)": 先旋转再移动

"rotateY(180deg) translateX(150px)": 先移动再旋转

为什么两者只是前后顺序不同 结果却是相反的呢?
这就涉及到了 中心点的问题 transform-origin
transform-origin 变换原点 center center;

关键字: top bottom center left right;
具体的长度单位(em,rem,px...)

会受到原点影响的变换有:rotate、skew、scale
translate不受影响

第一个是先根据中心原点旋转180度 再向右移动150pxbr
第二个向右移动150px 中心点未改变 再旋转180deg

还有一点需要注意:

在js中没有办法 通过计算后样式 获取到 transform中的相关操作,只能获取到矩阵

getComputedStyle(XX)['transform'] 得到的是 matrix3d(...)

关于 transform的所有操作,通过封装cssTransform来进行操作,
在 cssTransform 中来记录 对transform的每一步操作,相当于对象赋值。获取的时候,就获取 cssTransform中的记录

function css(element, attr , val){
// 通过判断 归纳transform 属性 直接跳到cssTramsform 剩下的直接常规方法处理
if(attr == "rotate" || attr == "rotateX"
|| attr == "rotateY" ||attr == "rotateZ"
|| attr == "scale" || attr == "scaleX"
|| attr == "scaleY" || attr == "skewX"
|| attr == "skewY" || attr == "translateX"
|| attr == "translateY" || attr == "translateZ" ){
return cssTransform(element, attr, val);
}
if(arguments.length == 2){
var val = getComputedStyle(element)[attr];
if(attr=='opacity'){
val = Math.round(val*100);
}
return parseFloat(val);
}
if(attr == "opacity") {
element.style.opacity= val/100;
} else {
element.style[attr]= val + "px";
}
}
function cssTransform(el, attr, val) {
if(!el.transform){
el.transform = {}
}
// 如果val为空 为获取值
if(typeof val == "undefined"){
if(typeof el.transform[attr] == "undefined"){
switch(attr) {
case "scale":
case "scaleX":
case "scaleY":
el.transform[attr] = 100;
break;
default:
el.transform[attr] = 0;
}
}
return el.transform[attr];
} else {
// 设置值 原理就是对象的赋值
var transformVal = "";
el.transform[attr] = Number(val);
for(var s in el.transform){
switch(s){
case "rotate":
case "rotateX":
case "rotateY":
case "rotateZ":
case "skewX":
case "skewY":
transformVal += " "+s+"("+el.transform[s]+"deg)";
break;
case "translateX":
case "translateY":
case "translateZ":
transformVal += " "+s+"("+el.transform[s]+"px)";
break;
case "scale":
case "scaleX":
case "scaleY":
transformVal += " "+s+"("+el.transform[s]/100+")";
break;
}
}
el.style.WebkitTransform = el.style.transform = transformVal;
}
}

加下来介绍核心库:m.Tween.js运动函数
使用如下:

MTween({
el: div, // 目标元素
target: { // 期望最后变化的值
scale: 200,
translateX: 200,
translateY: 200,
rotate: 360
},
time: 1000, // 动画执行时间
type: "backOut", // 动画特效 贝塞尔曲线
callBack: function(){ // 动画执行结束的回调
console.log("动画执行完了");
},
callIn: function(){ // 动画执行过程的回调
console.log("动画执行中");
}
})

实现的代码也很简单

function MTween(init){
var t = 0;
var b = {};
var c = {};
var d = init.time / 20;
for(var s in init.target){
b[s] = css(init.el, s);
c[s] = init.target[s] - b[s];
}
clearInterval(init.el.timer);
init.el.timer = setInterval(
function(){
t++;
if(t>d){
clearInterval(init.el.timer);
init.callBack&&init.callBack.call(init.el);
} else {
init.callIn&&init.callIn.call(init.el);
for(var s in b){
var val = (Tween[init.type](t,b[s],c[s],d)).toFixed(2);
css(init.el, s, val);
}
}
},20);
}

以上只是基础知识,为下面的教程铺垫

正文开始:

1、安踏图标转动,来回变化,消失

2、碎片,云朵不规则圆柱转动

3、主体,浮层 圆柱形滚动入场

4、移动事件,陀螺仪,横竖屏事件

// 整体Html结构
<div id="pageBg"></div>
<div id="view">
<div id="logo1">
<div class="logoImg">
<img src="load/logo.png">
</div>
<p class="logoText">已加载 0%</p>
</div>
<div id="main">
<div id="tZ">
<div id="panoBg"></div>
<div id="cloud"></div>
<div id="pano"></div>
</div>
</div>
</div>

1、安踏图标转动,来回变化,消失

分析: 安踏图标有三个 分别为 logo1 logo2 logo3 (logo2 logo3 为动态生成,并提前赋值属性,加上360度旋转动画)
logo1 使用css3动画animation 360度转动 1s后透明度为0 并删除
logo2 由 translateZ : -1000 经过300ms 变为0 向前移动;接着经过800ms 变为-1000 向后移动
logo3 在logo2 删除后出现 由远到近 再接着消失

其实代码很简单 就是用下面的模型代码实现

MTween({
el: logo1,
target: {
opacity: 0 // 将要最终变化的值
},
time: 1000,
type: 'easeOut',
callBack: function() { // 运动结束的执行动作
view.removeChild(logo1)
css(logo2, 'opacity', 100) // 显示logo2
// 接下来做logo2动作 以此类推
MTween({
el: logo2,
target: {
translateZ: 0
},
time: 300,
type: 'easeBoth',
callBack: anmt2
})
}
})

2、碎片,云朵不规则圆柱转动

分析:将9张碎片图片乘3 然后设置随机的 rotateY rotateX translateZ translateY 变成一个随机圆柱排布,然后在碎片的主层加上 rotateY 旋转动画,再用动画控制translateZ 向后移动
祥云入场: 利用 sin cos R 计算translateX translateZ,然后在云层主层加上 rotateY 旋转动画,再用动画控制translateZ 向后移动

碎片代码

//基础框架版本 排成一圈
for (var i = 0; i < 27; i++) {
var R = 10 + Math.round(Math.random()*240);
var deg = Math.round(Math.random()*360)
css(span, 'rotateY', deg)
css(span, 'translateZ', R)
}
// 添加上下分布
css(logo4, "translateZ", -2000)
css(logo4, "scale", 0)
for (var i = 0; i < 27; i++) {
var xR = 20 + Math.round(Math.random() * 240) // 圆柱碎片的X半径
var xDeg = Math.round(Math.random() * 360)
var yR = 10 + Math.round(Math.random() * 240) // 圆柱碎片的Y半径
var yDeg = Math.round(Math.random() * 360)
css(span, "rotateY", xDeg);
css(span, "translateZ", xR);
css(span, "rotateX", yDeg);
css(span, "translateY", yR)
}
// 从远到近的移动
MTween({
el: logo4,
target: {
translateZ: 0,
scale: 100
},
time: 500,
type: "easeOutStrong",
callBack: function() {
setTimeout(function() { //从近到远
MTween({
el: logo4,
target: {
translateZ: -1000,
scale: 20
},
...
})

祥云代码
这里需要每一片云朵都面对我们自己

这里知道每一个R deg,便能求得x, z
x = Math.sin(deg * Math.PI / 180) * R
z = Math.cos(deg * Math.PI / 180) * R

  var span = document.createElement("span");
span.style.backgroundImage = 'url(' + imgData.cloud[i % 3] + ')';
var R = 200 + (Math.random() * 150) // 设置随机半径
var deg = (360 / 9) * i // 圆柱各个角度
var x = Math.sin(deg * Math.PI / 180) * R // sin求得X
var z = Math.cos(deg * Math.PI / 180) * R // cos求得Z
var y = (Math.random() - .5) * 200 // 上下分布
css(span, "translateX", x)
css(span, "translateZ", z)
css(span, "translateY", y)
...
// 设置动画
MTween({
el: cloud,
target: {
rotateY: 540
},
time: 3500,
type: "easeIn",
callIn: function() { // 这里需要用到运动过程的回调 将祥云外层的角度赋予内层祥云的每个角度
var deg = -css(cloud, "rotateY");
for (var i = 0; i < cloud.children.length; i++) {
css(cloud.children[i], "rotateY", deg);
}
}
})

3、主体,浮层 圆柱形滚动入场


这里的图片是由20张分割好的宽129px的图片组成

每张图片的角度deg为360/20,这样就能得到中心点距离每张图片的距离,利用数学的tan公式 R = (width / 2) / Math.tan((deg/ 2 )* Math.PI / 180)

var panoBg = document.querySelector('#panoBg')
var width = 129 // 一张图片宽度
var deg = 360 / imgData.bg.length // 圆柱图片角度
var R = parseInt((width / 2) / Math.tan((deg/ 2 )* Math.PI / 180) - 1) // tan@ = 对边(R) / 临边(W/2)
var startDeg = 180; // 开始角度
for (var i = 0; i < imgData.bg.length; i++) {
var span = document.createElement("span");
css(span, 'rotateY', startDeg)
css(span, 'translateZ', -R)
span.style.backgroundImage = "url(" + imgData.bg[i] + ")";
panoBg.appendChild(span);
startDeg -= deg // 每张图片角度递减
}

设置主体从远到近 类似画轴显示出来,在span初始化时候都设置display="none",然后设置定时器依次打开

var timer = setInterval(function() {
panoBg.children[num].style.display = "block";
num++
if (num >= panoBg.children.length) {
clearInterval(timer)
}
}, 3600 / 2 / 20)

设置漂浮层
漂浮层相对简单一些,动态创建漂浮层,设置初始translateX translateZ,遍历对应的浮层,设置上面求得的半径距离,角度

  var pano = document.querySelector('#pano'); // 浮层容器
var deg = 18; // 差值角度
var R = 406; // 上图计算的R
var nub = 0; // 计数
var startDeg = 180; // 初始角度
css(pano, "rotateX", 0);
css(pano, "rotateY", -180);
css(pano, "scale", 0);
var pano1 = document.createElement("div");
pano1.className = "pano";
css(pano1, "translateX", 1.564);
css(pano1, "translateZ", -9.877);
for (var i = 0; i < 2; i++) {
var span = document.createElement("span");
span.style.cssText = "height:344px;margin-top:-172px;";
span.style.background = "url(" + imgData["pano"][nub] + ")";
css(span, "translateY", -163); // 设定固定的值
css(span, "rotateY", startDeg); // 角度逐级递减
css(span, "translateZ", -R);
nub++;
startDeg -= deg;
pano1.appendChild(span)
}
var pano2 = document.createElement("div");
pano2.className = "pano";
css(pano2, "translateX", 20.225);
css(pano2, "translateZ", -14.695);
for (var i = 0; i < 3; i++) {
var span = document.createElement("span");
span.style.cssText = "height:326px;margin-top:-163px;";
span.style.background = "url(" + imgData["pano"][nub] + ")";
css(span, "translateY", 278);
css(span, "rotateY", startDeg);
css(span, "translateZ", -R);
nub++;
startDeg -= deg;
pano2.appendChild(span)
}

4、移动事件,陀螺仪,横竖屏事件


移动事件需要监听三个事件touchstart touchmove touchend
初始化 按下的点startPoint, 主层角度panoBgDeg, 移动一度变化多少px的系数scale,主层深度startZ,最后角度lastDeg,最后差距lastDis

手指按下 touchstart

 document.addEventListener('touchstart', function(e) {
startPoint.x = e.changedTouches[0].pageX //手指初始位置
startPoint.y = e.changedTouches[0].pageY //
panoBgDeg.x = css(panoBg, 'rotateY') //主体容器左右移动 rotateY便是X轴
panoBgDeg.y = css(panoBg, 'rotateX')
})

手指移动 touchmove

document.addEventListener('touchmove', function(e) {
var nowDeg = {}
var nowDeg2 = {} // 悬浮层也需要移动
var nowPoint = {}
nowPoint.x = e.changedTouches[0].pageX; //变化的位置
nowPoint.y = e.changedTouches[0].pageY;
var dis = {}
dis.x = nowPoint.x - startPoint.x // 移动的距离X
dis.y = nowPoint.y - startPoint.y
var disDeg = {}
disDeg.x = -(dis.x / scale.x) // 距离转度数
disDeg.y = dis.y / scale.y
nowDeg.y = panoBgDeg.y + disDeg.y // 开始角度 + 移动角度
nowDeg.x = panoBgDeg.x + disDeg.x
nowDeg2.x = panoBgDeg.x + (disDeg.x) * 0.95 // 浮层的稍微偏动
nowDeg2.y = panoBgDeg.y + (disDeg.y) * 0.95
if (nowDeg.y > 45) {
nowDeg.y = 45
} else if (nowDeg.y < -45) {
nowDeg.y = -45
} if (nowDeg2.y > 45) {
nowDeg2.y = 45
} else if (nowDeg2.y < -45) {
nowDeg2.y = -45
}
lastDis.x = nowDeg.x - lastDeg.x //进行差距计算
lastDeg.x = nowDeg.x
lastDis.y = nowDeg.y - lastDeg.y
lastDeg.y = nowDeg.y
css(panoBg, "rotateX", nowDeg.y); // 进行主体角度赋值
css(panoBg, "rotateY", nowDeg.x);
css(pano, "rotateX", nowDeg2.y); // 悬浮层角度
css(pano, "rotateY", nowDeg2.x);
var disZ = Math.max(Math.abs(dis.x), Math.abs(dis.y))
if (disZ > 300) {
disZ = 300
}
css(tZ, 'translateZ', startZ - disZ) // 控制拖拉远近距离
})

手指抬起 touchend

document.addEventListener('touchend', function(e) {
var nowDeg = {
x: css(panoBg, "rotateY"), // 获取结束角度
y: css(panoBg, "rotateX")
};
var disDeg = {
x: lastDis.x * 10, //
y: lastDis.y * 10
}
MTween({
el: tZ,
target: {
translateZ: startZ // 移动后回来 变近
},
time: 700,
type: "easeOut"
})
MTween({
el: panoBg,
target: {
rotateY: nowDeg.x + disDeg.x // 主体缓冲
},
time: 800,
type: "easeOut"
})
MTween({
el: pano,
target: {
rotateY: nowDeg.x + disDeg.x // 悬浮层缓冲
},
time: 800,
type: "easeOut",
callBack: function() {
window.isTouch = false
window.isStart = false
}
})
})
}

设置景深随不同屏幕适配进行调整

function setPerc() {
resteview()
window.onresize = resteview function resteview() {
var view = document.querySelector('#view') // 最外层
var main = document.querySelector('#main')
var deg = 52.5
var height = document.documentElement.clientHeight;
var R = Math.round(Math.tan(deg / 180 * Math.PI) * height * .5);
view.style.WebkitPerspective = view.style.perspective = R + "px"; // 设置景深
css(main, 'translateZ', R)
}
}

陀螺仪 横竖屏事件

陀螺仪基础

 window.addEventListener('deviceorientation', function(e) {
e.beta // 左右
e.gamma // 上下
})

横竖屏基础

 window.addEventListener('orientationchange', function(e) {
window.orientation // 0 90 -90 180 代表四个方向
})

这里需要解决触摸事件的冲突,需要定义一个全局的isTouch判断,遇到触摸就终止陀螺仪事件引起的变化。
同时需要注意横竖屏会把陀螺仪的beta gamma 改变

 dir = window.orientation
switch (dir) {
case 0:
x = e.beta;
y = e.gamma;
break;
case 90:
x = e.gamma;
y = e.beta;
break;
case -90:
x = -e.gamma;
y = -e.beta;
break;
case 180:
x = -e.beta;
y = -e.gamma;
break;
}

开始倾斜时,记录开始的陀螺仪位置,主体层的位置。
移动时候和触摸一样进行距离差值计算,并进行相加赋予主体层的变化。然后进行远近动画,主体移动动画,悬浮层动画。

 var nowTime = Date.now()
// 检测陀螺仪 转动时间 与插件的20ms 兼容
if (nowTime - lastTime < 30) {
return
}
lastTime = nowTime
// 角度倾斜
if (!isStart) {
//start
isStart = true;
start.x = x
start.y = y
startEl.x = css(pano, 'rotateX')
startEl.y = css(pano, 'rotateY')
} else {
// move
now.x = x
now.y = y var dis = {}
dis.x = now.x - start.x
dis.y = now.y - start.y var deg = {}
deg.x = startEl.x + dis.x
deg.y = startEl.y + dis.y if (deg.x > 45) {
deg.x = 45;
} else if (deg.x < -45) {
deg.x = -45;
} var disXZ = Math.abs(Math.round((deg.x - css(pano, 'rotateX')) * scale))
var disYZ = Math.abs(Math.round((deg.y - css(pano, "rotateY")) * scale)) var disZ = Math.max(disXZ, disYZ)
if (disZ > 300) {
disZ = 300
}
MTween({
el: tZ,
target: {
translateZ: startZ - disZ
},
time: 300,
type: 'easeOut',
callBack: function(){
MTween({
el:tZ,
target:{
translateZ: startZ // 进行缓冲动画
},
time: 400,
type: "easeOut"
})
}
}) MTween({
el: pano,
target: {
rotateX: deg.x,
rotateY: deg.y
},
time: 800,
type: 'easeOut'
}) MTween({
el: panoBg,
target: {
rotateX: deg.x,
rotateY: deg.y
},
time: 800,
type: 'easeOut'
})

以上便是主要代码,最好自己运行调试下,运用好动画函数,理解每一个步骤。
前端实现3D VR 还有更牛的Three.js, A-Frame。继续深究
该课程是由[妙味课堂]提供的,可以从基础开始学习。

前端的3D(css3版本)--淘宝造物节3D创景的制作的更多相关文章

  1. 前端的3D(css3版本)

    其实是依托Css3的功劳,先上一个例子 代码地址:链接: https://pan.baidu.com/s/1sldhljJ 密码: i6qh 这动画纵有万般变化,也离不开以下几个属性 transfor ...

  2. 【前端开发】nrm切换淘宝镜像&nvm管理node版本及切换

    说明:nrm是切换淘宝镜像用的,nvm是node的版本切换用的(可在自己电脑安装多个版本node,便于不同项目的支持) 一.nrm的安装及常见命令: 安装nrmnpm install -g nrm 查 ...

  3. android电子书App、自定义图表、仿腾讯漫画App、仿淘宝优惠券、3D选择容器等源码

    Android精选源码 仿支付宝记账本功能,饼状图:数字键盘 android一款功能完善的电子书应用源码 Android自定义图标库,使用方便,扩展性强 android 3D立体无限旋转容器源码 an ...

  4. 火云开发课堂 - 《使用Cocos2d-x 开发3D游戏》系列 第四节:3D公告板

    <使用Cocos2d-x 开发3D游戏>系列在线课程 第四节:3D公告板 视频地址:http://edu.csdn.net/course/attend/1330/20804 交流论坛:mo ...

  5. 淘宝的前端类库-KISSY

    KISSY(淘宝) KISSY是淘宝的前端类库,几乎在淘宝的每个页面上都能看到它的身影. KISSY提供稳定的核心,包括 oo.dom.Event.Anim.Ajax 等:强大且易用的脚本加载器,特有 ...

  6. 【转】淘宝技术牛p博客整理

    转自:http://blog.csdn.NET/zdp072/article/details/19574793 淘宝技术委员会是由淘宝技术部高级技术人员组成的一个组织,共分为Java分会.C/C++分 ...

  7. 淘宝接口 TopAPi

    演示一下调用淘宝的接口,让大家心里有个数, 很简单,新建一个工程,拖一个IDHttp,Button和Memo到窗体上去 然后在这个Button的OnClick事件中写入如下代码: [delphi] v ...

  8. zz开源 MNN:淘宝在移动 AI 上的实践

    开源 MNN:淘宝在移动 AI 上的实践   陈以鎏(离青) 阅读数:40612019 年 6 月 28 日   随着深度学习的快速发展和端侧设备算力的不断提升,原本在云端执行的推理预测工作正在部分迁 ...

  9. TP5+阿里云OSS上传文件第三节,实现淘宝上传商品图片

    **TP5+阿里云OSS上传文件第三节,实现淘宝上传商品图片首先我们来看看淘宝的功能和样式:** 之后看看制作完成的演示:(由于全部功能弄成GIF有点大,限制上传大小好像在1M之内,压缩之后也有1.9 ...

随机推荐

  1. [lottery anayliser]lottery anayliser

    抓取网页,获得获奖信息 #!/usr/bin/python import urllib2 import re import time def spider(url): ""&quo ...

  2. DOM基本代码二

    ------------------------------- <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xh ...

  3. POJ 1753 BFS

    Flip Game Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 44450   Accepted: 19085 Descr ...

  4. Qt ------ 内存回收机制、new对象的回收

    写在前面的总结: 建议:对于不能指定父对象的对象(对象通过moveToThread()移入其他线程.没有继承QObject的类产生的对象),在其他线程通过deleteLater()内存回收,其他通过指 ...

  5. eclipse中编写代码时如何自动提示变量名?

    打开 Eclipse  -> Window -> Perferences -> Java -> Editor -> Content Assist,在右边最下面一栏找到 a ...

  6. 图论:Floyd-多源最短路、无向图最小环

    在最短路问题中,如果我们面对的是稠密图(十分稠密的那种,比如说全连接图),计算多源最短路的时候,Floyd算法才能充分发挥它的优势,彻彻底底打败SPFA和Dijkstra 在别的最短路问题中都不推荐使 ...

  7. CAS单点登录原理

    转自 https://www.cnblogs.com/lihuidu/p/6495247.html 1.基于Cookie的单点登录的回顾        基于Cookie的单点登录核心原理: 将用户名密 ...

  8. 【lydsy1407】拓展欧几里得求解不定方程+同余方程

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1407 题意: 有n个野人,野人各自住在第c[i]个山洞中(山洞成环状),每年向前走p[i] ...

  9. Android中自定义属性attr.xml的格式详解

    1. reference:参考某一资源ID.     (1)属性定义:             <declare-styleable name = "名称">      ...

  10. linux进程管理-定时定期执行任务

     0.计划任务的命令: at 安排作业在某一时刻执行 batch 安排作业在系统负载不重时执行 crontab 安排周期性运行的作业 1.at命令用法: 安排命令或者多个命令在指定的时间运行一次 语法 ...