天天看點

HTML5+CSS3進階動畫的應用實踐

我們大概都知道css可以用來作平面旋轉、扭曲、放大縮小、平移。。。并且用起來幾乎都得心應手。

但目前來說,3D效果的“進階”動畫似乎更受歡迎一些,而且我們也确實需要。

這不,前兩天筆者就在項目中給“翻轉動畫”增加了一個3D效果,看起來賊爽:

HTML5+CSS3進階動畫的應用實踐

這個動畫實作所用到的3D盒子模型是現在3D模型中最常用的一個 —— 不過我們先拿其中兩個面分析:

  1. 首先,要實作這個功能,我們從外往裡看:把文字所在部分看作一個盒子的話,前後兩個橫線并不屬于這個盒子才對,那麼,很自然就想到了——​

    ​::after​

    ​​ 和​

    ​::before​

    ​ 僞元素;
  2. 其次,兩個文字分别在兩個div上,那麼就需要有一個可以附帶​

    ​overflow: hidden​

    ​ 的盒子 —— 不能加到上面的盒子中,因為after和before不屬于div!
  3. 最後是兩個元素的翻轉效果:我們需要知道的是,為了性能考慮,我們最好是對整個盒子進行翻轉,而不是對兩個文字div附加動畫
事實上,transform動畫中的屬性表示的含義更多的是“過渡多少”而不是“過渡到哪裡”!

那麼,這個層級關系就很明了了:

<!--僞元素裝飾盒子-->
<div class="pic_border">
    <!--overflow-hidden盒子-->
    <div class="pic_box">
        <!--transition過渡盒子-->
        <div class="pic_item">
            <div class="pic_text">music</div>
            <div class="pic_back">此時此刻,非我莫屬</div>
        </div>
    </div>
</div>      

按照上面所說,我們很容易為它添加對應的CSS:

.pic_border{
    position: relative;
}
.pic_border::before{
    content: '';
    position: absolute;
    top: 50%;
    left: 0;
    width: 43vw;
    height: 1px;
    background-color: red;
}
.pic_border::after{
    content: '';
    position: absolute;
    top: 50%;
    right: 0;
    width: 43vw;
    height: 1px;
    background-color: red;
}
@media screen and (max-width: 1100px) {
    .pic_border::before,.pic_border::after{
        width: 20vw;
    }
}
.pic_box{
    display: inline-block;
    height: 70px;
    margin-left: calc(50% - 70px);
    overflow: hidden;
    perspective: 2000px;
    cursor: pointer;
    user-select: none;
}
.pic_item{
    width: 100%;
    height: 100%;
    transform-style: preserve-3d;
    transition: all .7s ease;
}
.pic_text,.pic_back{
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
}
.pic_text{
    transform: rotateX(0deg) translateZ(-21.9px);
}
.pic_back{
    transform: rotateX(90deg) translateZ(-15px);
}
.pic_box:hover .pic_item{
    transform: rotateX(-90deg);
}
.pic_box:active .pic_item{
    transform: rotateX(-90deg);
}      

需要注意的是:3D效果是一定要有Z軸參與的! 不然會顯得很“尴尬”

HTML5+CSS3進階動畫的應用實踐

有了簡單的上下翻滾,我們還可以實作“跟随滑鼠上下左右翻滾”動畫,就是所謂的“滑鼠從哪裡進入盒子,盒子就往哪個方向翻轉” —— 有兩種實作方式:

  1. 在最外層盒子中加四個方向的i或span标簽,用來判斷滑鼠從哪裡進入,JS控制盒子做對應的rotateX/Y;
  2. 借助數學庫與“matrix”:
<div class="block" id="block">
    <div class="face front"></div>
    <div class="face back"></div>
    <div class="face up"></div>
    <div class="face down"></div>
    <div class="face left"></div>
    <div class="face right"></div>
</div>      
.block {
    position: absolute;
    transform-style: preserve-3d;
    width: 100px;
    height: 100px;
    transform-origin: 50px 50px;
}
.front {
    background: fuchsia;
}

.back {
    transform: translate3d(0, 0, 100px) rotateY(180deg);
    background: red;
}
.left {
    transform-origin: 100% 50% 0px;
    transform: rotateY(90deg);
    background: aqua;
}
.right {
    transform-origin: 0% 50% 0px;
    transform: rotateY(-90deg);
    background: blueviolet;
}
.up {
    transform-origin: 50% 0% 0px;
    transform: rotateX(90deg);
    background: darkorange;
}
.down {
    transform-origin: 50% 100% 0px;
    transform: rotateX(-90deg);
    background: darkviolet;
}      

為了便于觀察,我們為讓魔方格子旋轉起來:

滑鼠滑動分為左、右、上、下滑動,每種滑動對應一種方向的格子旋轉。

  • 從右往左:繞 Y 軸旋轉 θ 角
  • 從左往右:繞 Y 軸旋轉 -θ 角
  • 從上往下:繞 X 軸旋轉 θ 角
  • 從下往上:繞 X 軸旋轉 -θ 度

當然旋轉需要有一個參照點,預設盒子中心。我們可以借助庫函數将生成的矩陣轉化為 CSS 中 transform 的 matrix3d 屬性值。

var currentQ = {x:0, y:0, z:0, w:1};
var lastQ = {x:0, y:0, z:0, w:1};
var currentMatrix = matrix.identity();
var l = Math.sqrt(dx * dx + dy * dy);
if(l <= 0) return;
var x = dx / l, y = dy / l;
var axis = {x: x, y: y, z: 0};
var q = matrix.fromAxisAndAngle(axis, l);
currentQ = matrix.multiplyQuaternions(q, lastQ);
currentMatrix = matrix.makeRotationFromQuaternion(currentQ);      

通過上述方式我們計算出了目前旋轉矩陣 currentMatrix,接下來,我們使用上面介紹的矩陣轉化成對應 css 的函數,生成對應的 transform 屬性:

// 将矩陣轉化為transform matrix 屬性值。
function matrix2css(m){
    var style = 'matrix(';
    if(m.length == 16){
        style = 'matrix3d('
    }
    for(let i =0; i< m.length; i++){
        style += m[i];
        if(i !== m.length - 1){
            style += ','
        }else{
            style +=')'
        }
    }
    return style;
}      
var style = matrix2css(currentMatrix);      

最後将生成的樣式應用到魔方格子上:

document.querySelector('#block').style.transform = style;      

這樣就實作了一個美妙的動畫盒子!

幀動畫在canvas中的應用

除去CSS-transform和animation在項目中的大放異彩,canvas+CSS的動畫方式也得到了很多人的支援!而canvas中實作動畫的最好方式不是離屏技術、不是canvas動畫庫,而是幀動畫!

我們通常通過requestAnimFrame控制一張圖檔上的顯示區域的位置進而達到“僞動畫”!

比如:

HTML5+CSS3進階動畫的應用實踐
//調用方js部分内容
var starPic=new Image()
starPic.src="上面圖檔位址"

var lastTime,deltaTime;

var stardog=new starObj()
stardog.init()

lastTime=Date.now()

gameloop()

function gameloop(){
  window.requestAnimFrame(gameloop)
  var now=Date.now()
  deltaTime=now-lastTime
  lastTime=now
  drawStars()
}      
//真正控制動畫的js檔案
var satrObj=function(){
  this.x;
  this.y;
  this.picNo;
  this.timer;
}
starObj.prototype.init=function(){
  this.x=Math.random()*630+100;   //630:圖檔寬度
  this.y=Math.random()*70+150;   //70:圖檔高度
  this.picNo=0;
  this.timer=0;
}
starObj.prototype.update=function(){
  this.timer+=deltaTime;
  if(this.timer>50){
    this.picNo+=1;
    this.picNo%=7;
    this.timer=0;
  }
}
starObj.prototype.draw=function(){
  ctx.drawImage(starPic,0,0,this.picNo*70,70,this.x,this.y,70,70)
}

function drawStars(){
  stardog.update();
  stardog.draw();
}      
HTML5+CSS3進階動畫的應用實踐

毫無疑問的是:這種方式對UI和前端的結合開始有了要求。

(筆者前段時間研究支付寶春節活動發現:裡面采用的也是“前端引入動畫檔案”的方式!)

/**
  el:需要彈幕的元素(此區域的最外層元素),需要加“#”或“.”
  text:彈幕文字
  width:需要彈幕的元素(此區域的最外層元素)的寬
  height:需要彈幕的元素(此區域的最外層元素)的高
  step:可選,用于控制彈幕速度
*/
function canvasTextAnim(el,text,width,height,step=5){
  let canvas=document.createElement("canvas");
  canvas.setAttribute('width',width);
  canvas.setAttribute('height',height);
  canvas.style.cssText="position:absolute;top:0;left:0;z-index:10000000";
  document.querySelector(el).appendChild(canvas);
  let ctx=canvas.getContext('2d');
  let w=canvas.width;
  let wid=w-1;
  let heigh=Math.random()*canvas.height+1;
  let stepd=Math.random()*step+2;
  ctx.beginPath();
  setInterval(()=>{
    ctx.clearRect(0,0,w,canvas.height);
    ctx.fillText(text,wid-stepd,heigh);
    wid-=stepd;
    if(wid<-ctx.measureText(text).width){
      document.querySelector(el).removeChild(canvas);
    }
  },90)
}      

繼續閱讀