摘要:同時(shí)需要注意橫豎屏?xí)淹勇輧x的改變開(kāi)始傾斜時(shí),記錄開(kāi)始的陀螺儀位置,主體層的位置。檢測(cè)陀螺儀轉(zhuǎn)動(dòng)時(shí)間與插件的兼容角度傾斜進(jìn)行緩沖動(dòng)畫(huà)以上便是主要代碼,最好自己運(yùn)行調(diào)試下,運(yùn)用好動(dòng)畫(huà)函數(shù),理解每一個(gè)步驟。前端實(shí)現(xiàn)還有更牛的。
前端的3D(css3版本),其實(shí)是依托Css3的功勞,先上一個(gè)例子 http://antario.act.qq.com/
代碼地址:鏈接:https://pan.baidu.com/s/1XYI-... 密碼: thw9
這動(dòng)畫(huà)縱有萬(wàn)般變化,也離不開(kāi)以下幾個(gè)屬性
transform (元素2D 3D轉(zhuǎn)換)
translate,3d,X,Y,Z (移動(dòng)距離) scale,3d,X,Y,Z (縮放比例) rotate,3d,X,Y,Z (旋轉(zhuǎn)角度) skew,X,Y (傾斜角度)
transform-origin (允許被轉(zhuǎn)換元素位置)
left center right length %
transform-style (被嵌套元素在3D空間中顯示)
flat (2d) presever-3d (3d)
perspective (3D元素透視效果 俗稱"景深")
number
perspective-origin (設(shè)置3D基數(shù)位置 x,y)
top center right length %
backface-visibility (元素不面對(duì)屏幕是否可見(jiàn))
visible hidden
這里寫(xiě)一個(gè)變化的例子,幫助理解
以上例子只是單一的變化 如果多個(gè)變化一起執(zhí)行 遵守 “慢寫(xiě)的先執(zhí)行”
比如:
原始圖片:
"translateX(150px) rotateY(180deg)": 先旋轉(zhuǎn)再移動(dòng)
"rotateY(180deg) translateX(150px)": 先移動(dòng)再旋轉(zhuǎn)
為什么兩者只是前后順序不同 結(jié)果卻是相反的呢?
這就涉及到了 中心點(diǎn)的問(wèn)題 transform-origin
transform-origin 變換原點(diǎn) center center;
關(guān)鍵字: top bottom center left right; 具體的長(zhǎng)度單位(em,rem,px...)
會(huì)受到原點(diǎn)影響的變換有:rotate、skew、scale
translate不受影響
第一個(gè)是先根據(jù)中心原點(diǎn)旋轉(zhuǎn)180度 再向右移動(dòng)150pxbr
第二個(gè)向右移動(dòng)150px 中心點(diǎn)未改變 再旋轉(zhuǎn)180deg
還有一點(diǎn)需要注意:
在js中沒(méi)有辦法 通過(guò)計(jì)算后樣式 獲取到 transform中的相關(guān)操作,只能獲取到矩陣
getComputedStyle(XX)["transform"] 得到的是 matrix3d(...)
關(guān)于 transform的所有操作,通過(guò)封裝cssTransform來(lái)進(jìn)行操作,
在 cssTransform 中來(lái)記錄 對(duì)transform的每一步操作,相當(dāng)于對(duì)象賦值。獲取的時(shí)候,就獲取 cssTransform中的記錄
function css(element, attr , val){ // 通過(guò)判斷 歸納transform 屬性 直接跳到cssTramsform 剩下的直接常規(guī)方法處理 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 { // 設(shè)置值 原理就是對(duì)象的賦值 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; } }
加下來(lái)介紹核心庫(kù):m.Tween.js運(yùn)動(dòng)函數(shù)
使用如下:
MTween({ el: div, // 目標(biāo)元素 target: { // 期望最后變化的值 scale: 200, translateX: 200, translateY: 200, rotate: 360 }, time: 1000, // 動(dòng)畫(huà)執(zhí)行時(shí)間 type: "backOut", // 動(dòng)畫(huà)特效 貝塞爾曲線 callBack: function(){ // 動(dòng)畫(huà)執(zhí)行結(jié)束的回調(diào) console.log("動(dòng)畫(huà)執(zhí)行完了"); }, callIn: function(){ // 動(dòng)畫(huà)執(zhí)行過(guò)程的回調(diào) console.log("動(dòng)畫(huà)執(zhí)行中"); } })
實(shí)現(xiàn)的代碼也很簡(jiǎn)單
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); }
以上只是基礎(chǔ)知識(shí),為下面的教程鋪墊
正文開(kāi)始:1、安踏圖標(biāo)轉(zhuǎn)動(dòng),來(lái)回變化,消失
2、碎片,云朵不規(guī)則圓柱轉(zhuǎn)動(dòng)
3、主體,浮層 圓柱形滾動(dòng)入場(chǎng)
4、移動(dòng)事件,陀螺儀,橫豎屏事件
// 整體Html結(jié)構(gòu)1、安踏圖標(biāo)轉(zhuǎn)動(dòng),來(lái)回變化,消失已加載 0%
分析: 安踏圖標(biāo)有三個(gè) 分別為 logo1 logo2 logo3 (logo2 logo3 為動(dòng)態(tài)生成,并提前賦值屬性,加上360度旋轉(zhuǎn)動(dòng)畫(huà))
logo1 使用css3動(dòng)畫(huà)animation 360度轉(zhuǎn)動(dòng) 1s后透明度為0 并刪除
logo2 由 translateZ : -1000 經(jīng)過(guò)300ms 變?yōu)? 向前移動(dòng);接著經(jīng)過(guò)800ms 變?yōu)?1000 向后移動(dòng)
logo3 在logo2 刪除后出現(xiàn) 由遠(yuǎn)到近 再接著消失
其實(shí)代碼很簡(jiǎn)單 就是用下面的模型代碼實(shí)現(xiàn)
MTween({ el: logo1, target: { opacity: 0 // 將要最終變化的值 }, time: 1000, type: "easeOut", callBack: function() { // 運(yùn)動(dòng)結(jié)束的執(zhí)行動(dòng)作 view.removeChild(logo1) css(logo2, "opacity", 100) // 顯示logo2 // 接下來(lái)做logo2動(dòng)作 以此類推 MTween({ el: logo2, target: { translateZ: 0 }, time: 300, type: "easeBoth", callBack: anmt2 }) } })2、碎片,云朵不規(guī)則圓柱轉(zhuǎn)動(dòng)
分析:將9張碎片圖片乘3 然后設(shè)置隨機(jī)的 rotateY rotateX translateZ translateY 變成一個(gè)隨機(jī)圓柱排布,然后在碎片的主層加上 rotateY 旋轉(zhuǎn)動(dòng)畫(huà),再用動(dòng)畫(huà)控制translateZ 向后移動(dòng)
祥云入場(chǎng): 利用 sin cos R 計(jì)算translateX translateZ,然后在云層主層加上 rotateY 旋轉(zhuǎn)動(dòng)畫(huà),再用動(dòng)畫(huà)控制translateZ 向后移動(dòng)
碎片代碼
//基礎(chǔ)框架版本 排成一圈 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) } // 從遠(yuǎn)到近的移動(dòng) MTween({ el: logo4, target: { translateZ: 0, scale: 100 }, time: 500, type: "easeOutStrong", callBack: function() { setTimeout(function() { //從近到遠(yuǎn) MTween({ el: logo4, target: { translateZ: -1000, scale: 20 }, ... })
祥云代碼
這里需要每一片云朵都面對(duì)我們自己
這里知道每一個(gè)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) // 設(shè)置隨機(jī)半徑 var deg = (360 / 9) * i // 圓柱各個(gè)角度 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) ... // 設(shè)置動(dòng)畫(huà) MTween({ el: cloud, target: { rotateY: 540 }, time: 3500, type: "easeIn", callIn: function() { // 這里需要用到運(yùn)動(dòng)過(guò)程的回調(diào) 將祥云外層的角度賦予內(nèi)層祥云的每個(gè)角度 var deg = -css(cloud, "rotateY"); for (var i = 0; i < cloud.children.length; i++) { css(cloud.children[i], "rotateY", deg); } } })3、主體,浮層 圓柱形滾動(dòng)入場(chǎng)
這里的圖片是由20張分割好的寬129px的圖片組成
每張圖片的角度deg為360/20,這樣就能得到中心點(diǎn)距離每張圖片的距離,利用數(shù)學(xué)的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@ = 對(duì)邊(R) / 臨邊(W/2) var startDeg = 180; // 開(kāi)始角度 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 // 每張圖片角度遞減 }
設(shè)置主體從遠(yuǎn)到近 類似畫(huà)軸顯示出來(lái),在span初始化時(shí)候都設(shè)置display="none",然后設(shè)置定時(shí)器依次打開(kāi)
var timer = setInterval(function() { panoBg.children[num].style.display = "block"; num++ if (num >= panoBg.children.length) { clearInterval(timer) } }, 3600 / 2 / 20)
設(shè)置漂浮層
漂浮層相對(duì)簡(jiǎn)單一些,動(dòng)態(tài)創(chuàng)建漂浮層,設(shè)置初始translateX translateZ,遍歷對(duì)應(yīng)的浮層,設(shè)置上面求得的半徑距離,角度
var pano = document.querySelector("#pano"); // 浮層容器 var deg = 18; // 差值角度 var R = 406; // 上圖計(jì)算的R var nub = 0; // 計(jì)數(shù) 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); // 設(shè)定固定的值 css(span, "rotateY", startDeg); // 角度逐級(jí)遞減 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、移動(dòng)事件,陀螺儀,橫豎屏事件
移動(dòng)事件需要監(jiān)聽(tīng)三個(gè)事件touchstart touchmove touchend
初始化 按下的點(diǎn)startPoint, 主層角度panoBgDeg, 移動(dòng)一度變化多少px的系數(shù)scale,主層深度startZ,最后角度lastDeg,最后差距l(xiāng)astDis
手指按下 touchstart
document.addEventListener("touchstart", function(e) { startPoint.x = e.changedTouches[0].pageX //手指初始位置 startPoint.y = e.changedTouches[0].pageY // panoBgDeg.x = css(panoBg, "rotateY") //主體容器左右移動(dòng) rotateY便是X軸 panoBgDeg.y = css(panoBg, "rotateX") })
手指移動(dòng) touchmove
document.addEventListener("touchmove", function(e) { var nowDeg = {} var nowDeg2 = {} // 懸浮層也需要移動(dòng) var nowPoint = {} nowPoint.x = e.changedTouches[0].pageX; //變化的位置 nowPoint.y = e.changedTouches[0].pageY; var dis = {} dis.x = nowPoint.x - startPoint.x // 移動(dòng)的距離X dis.y = nowPoint.y - startPoint.y var disDeg = {} disDeg.x = -(dis.x / scale.x) // 距離轉(zhuǎn)度數(shù) disDeg.y = dis.y / scale.y nowDeg.y = panoBgDeg.y + disDeg.y // 開(kāi)始角度 + 移動(dòng)角度 nowDeg.x = panoBgDeg.x + disDeg.x nowDeg2.x = panoBgDeg.x + (disDeg.x) * 0.95 // 浮層的稍微偏動(dòng) 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 //進(jìn)行差距計(jì)算 lastDeg.x = nowDeg.x lastDis.y = nowDeg.y - lastDeg.y lastDeg.y = nowDeg.y css(panoBg, "rotateX", nowDeg.y); // 進(jìn)行主體角度賦值 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) // 控制拖拉遠(yuǎn)近距離 })
手指抬起 touchend
document.addEventListener("touchend", function(e) { var nowDeg = { x: css(panoBg, "rotateY"), // 獲取結(jié)束角度 y: css(panoBg, "rotateX") }; var disDeg = { x: lastDis.x * 10, // y: lastDis.y * 10 } MTween({ el: tZ, target: { translateZ: startZ // 移動(dòng)后回來(lái) 變近 }, 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 } }) }) }
設(shè)置景深隨不同屏幕適配進(jìn)行調(diào)整
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"; // 設(shè)置景深 css(main, "translateZ", R) } }
陀螺儀 橫豎屏事件
陀螺儀基礎(chǔ)
window.addEventListener("deviceorientation", function(e) { e.beta // 左右 e.gamma // 上下 })
橫豎屏基礎(chǔ)
window.addEventListener("orientationchange", function(e) { window.orientation // 0 90 -90 180 代表四個(gè)方向 })
這里需要解決觸摸事件的沖突,需要定義一個(gè)全局的isTouch判斷,遇到觸摸就終止陀螺儀事件引起的變化。
同時(shí)需要注意橫豎屏?xí)淹勇輧x的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; }
開(kāi)始傾斜時(shí),記錄開(kāi)始的陀螺儀位置,主體層的位置。
移動(dòng)時(shí)候和觸摸一樣進(jìn)行距離差值計(jì)算,并進(jìn)行相加賦予主體層的變化。然后進(jìn)行遠(yuǎn)近動(dòng)畫(huà),主體移動(dòng)動(dòng)畫(huà),懸浮層動(dòng)畫(huà)。
var nowTime = Date.now() // 檢測(cè)陀螺儀 轉(zhuǎn)動(dòng)時(shí)間 與插件的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 // 進(jìn)行緩沖動(dòng)畫(huà) }, 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" })
以上便是主要代碼,最好自己運(yùn)行調(diào)試下,運(yùn)用好動(dòng)畫(huà)函數(shù),理解每一個(gè)步驟。
前端實(shí)現(xiàn)3D VR 還有更牛的Three.js, A-Frame。繼續(xù)深究
該課程是由妙味課堂提供的,可以從基礎(chǔ)開(kāi)始學(xué)習(xí)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/83505.html
摘要:淘寶造物節(jié)的活動(dòng)頁(yè)就是全景的一個(gè)很贊的頁(yè)面,它將全景圖分割成等份,相鄰的元素構(gòu)成的夾角,相鄰兩側(cè)面相對(duì)于棱柱中心所構(gòu)成的夾角。 本文轉(zhuǎn)自凹凸實(shí)驗(yàn)室:https://aotu.io/notes/2016/08... showImg(https://segmentfault.com/img/remote/1460000011381045); 前言 3D 全景并不是什么新鮮事物了,但以前...
摘要:實(shí)現(xiàn)方法可參考這篇文章純打造的模型渲染器實(shí)現(xiàn)全景。天空盒子相信很多打造過(guò)或有了解過(guò)全景的同行們都知道這個(gè)概念。首先將創(chuàng)建好的六個(gè)面切割出來(lái),以命名標(biāo)記位置。柱形柱形全景也不算復(fù)雜。 前言 對(duì)的,本文就是著重介紹如何使用CSS3中的3D變換打造出H5中的3D效果。靈感來(lái)源于造物節(jié)團(tuán)隊(duì)的3d引擎,因?yàn)槭褂梅椒ū容^復(fù)雜,也沒(méi)有開(kāi)源的API文檔,于是想自己另外造個(gè)輪子,便開(kāi)始了相關(guān)內(nèi)容的學(xué)習(xí)和...
閱讀 782·2021-09-26 09:55
閱讀 2071·2021-09-22 15:44
閱讀 1480·2019-08-30 15:54
閱讀 1336·2019-08-30 15:54
閱讀 2681·2019-08-29 16:57
閱讀 526·2019-08-29 16:26
閱讀 2496·2019-08-29 15:38
閱讀 2133·2019-08-26 11:48