CSS動效集錦,視覺魔法的碰撞與融合(三)

  • 2020 年 2 月 18 日
  • 筆記

本文講述的原理和相關demo

  • 扇形DIV的使用——實現雷達掃描圖
  • DIV環形布局—實現loading圈
  • 動畫的向量合成—實現拋物線動畫
  • 無限滾動動畫—實現跑馬燈效果
  • perspective和transform的運用——實現卡片翻轉

話不多說,請看。

扇形DIV的使用——實現雷達掃描圖

在一些殺毒或文件掃描類的軟件上,我們可能會看到一些雷達掃描的UI樣式,例如下圖所示

如果我們要通過CSS該如何去實現話,我們的想法一般是先畫個扇形,然後給它加上漸變。

實現漸變的方式很簡單,但我們該如何實現一個扇形呢?

我們可以通過一些技巧實現這一點,請看:

沒錯,我們可以通過skew函數,將黃色的div傾斜,然後溢出部分通過overflow:hidden遮住就可以了。

  • 銳角扇形:deg<0,向右邊傾斜,即可得到銳角扇形
  • 鈍角扇形:deg>0, 向左邊傾斜,即可得到鈍角扇形

代碼如下

// CSS代碼  @keyframes rotateAnimate {    from {      transform: rotate(0deg) skew(-30deg)    }      to {      transform: rotate(360deg) skew(-30deg)    }  }    .fan-wrapper {    overflow: hidden;    position: relative;    margin: 100px;    width: 200px;    height: 200px;    border-radius: 50%;    background: red;  }    .fan {    position: absolute;    right: 0;    animation: rotateAnimate 2s linear infinite;    /* 這一行很重要,設置左下角為旋轉點 */    transform-origin: 0% 100%;    width: 100px;    height: 100px;    background: blue;  }   // HTML代碼   <div class="fan-wrapper">    <div class="fan"></div>  </div>

實現效果如下圖所示

(因為篇幅有限,漸變就不加了2333)

DIV環形布局—實現loading圈

loading加載條是常見的一種UI組件,如下圖所示

而要實現它,就需要考慮怎麼把一堆小圓等距地布局在一個「大圓」的邊框上,也就是DIV的環形布局的問題。

當然我們可以通過暴力測量解決,但很麻煩且不優雅,而且如果小圓的數量變化的話要重新測一遍。

我的解決辦法如下:

第一步:根據圓的數量計算相鄰圓和圓心形成的夾角

例如假設我們需要排列8個圓,那麼夾角為360度 / 8 = 45度。圖示如下,每個數字代表以該位置為圓心放一個小圓

第二步:以外部DIV左下角為原點,批量計算小圓圓心的橫縱坐標

批量算出所有圓的相對坐標,我們以編號8的圓為例,假設半徑R和X軸的逆時針夾角為θ,則有以下等式

(cos/sin可能有正負,而等式同樣成立)

第三步,外部div相對定位,內部小圓絕對定位,並且將步驟二中計算的X/Y作為小圓的bottom和left去設置

這一步也是批量完成,下圖以編號8的圓為例

代碼

CSS/HTML代碼如下:

我們在一個父div內部放8個子div。父div相對定位,而子div絕對定位

// CSS代碼  .circles {    position: relative;    margin: 50px;    width: 200px;    height: 200px;  }    .circle {    position: absolute;    width: 20px;    height: 20px;    border-radius: 50%;    background: black;  }   // HTML  <div class="circles">    <div class="circle circle1"></div>    <div class="circle circle2"></div>    <div class="circle circle3"></div>    <div class="circle circle4"></div>    <div class="circle circle5"></div>    <div class="circle circle6"></div>    <div class="circle circle7"></div>    <div class="circle circle8"></div>  </div>

JS代碼如下

第一步:編寫calcXYs方法: 以外部DIV左下角為原點,批量計算小圓圓心的橫縱坐標

/**   * R:大圓半徑,2*R = 外部正方形的邊長   * r:在大圓邊上等距排列的小圓的半徑   * counts: 圓的數量   * 返回值:   *  [   *    [x1,y1],   *    [x2,y2],   *    ...   *  ]   */  function calcXYs(R, r, counts) {    // 當前度數    let deg = 0;    // 單位度數,兩小圓和圓心的夾角    const pDeg = 360 / counts;    // 存放返回結果    const arr = [];    for (let i = 0; i < counts; i++) {      // 度數以單位度數遞增      deg = pDeg * i;      // Math.sin接收的參數以 π 為單位,需要根據360度 = 2π進行轉化      const proportion = Math.PI / 180;      // 以外部DIV左下角為原點,計算小圓圓心的橫縱坐標      let Y = R + R * Math.sin(proportion * deg);      let X = R + R * Math.cos(proportion * deg);      // 存放結果      arr.push([X, Y, deg]);    }    return arr;  }

第二步:編寫resizeCircles方法: 根據上一步的結果:調整絕對定位的小圓的位置

/**   * R,r,counts:含義同上   * selector: 獲取所有小圓的標誌符   * 作用:根據上一步的坐標計算結果,調整絕對定位的小圓的位置   */  function resizeCircles(selector, R, r, counts) {    // 獲取所有小圓NodeList的選擇器    let list = document.querySelectorAll(selector);    //調用calcXYs方法    const XYs = calcXYs(R, r, counts);    // 遍歷每個小圓的XY坐標    for (let i = 0; i < list.length; i++) {      const [X, Y] = XYs[i];      const e = list[i];      // 修改小圓距離外部DIV底部和左邊的距離      e.style.left = X + "px";      e.style.bottom = Y + "px";    }  }

最後我們只需要調用resizeCircles方法就可以啦

resizeCircles(".circle", 60, 20, 8);

實現效果如下

讓loading圖標動起來

好,現在布局完成了,那我們該怎麼去讓這個loading圖標「動起來」呢?

  1. 給每個圓設置animation實現明暗變化,例如可以設置黑色的背景色然後動態變化opacity
  2. animation屬性可以設置delay實現動畫延遲播放,我們依次給圓設置等距的delay,例如1s,2s,3s…
  3. 給animation屬性設置alternate,表示往複播放,設置infinite,表示無限循環播放
@keyframes k {    from {      opacity: 1;    }      to {      opacity: 0;    }  }  .circle1 {    animation: k 1s ease 0s alternate infinite;  }    .circle2 {    animation: k 1s ease 0.2s alternate infinite;  }    .circle3 {    animation: k 1s ease 0.4s alternate infinite;  }  // circle4 ~ circle8同理,delay以0.2s遞增

Demo

動畫的向量合成—實現拋物線動畫

在餓了么,或者淘寶天貓之類的購物外賣相關的APP里,我們可能會看到類似於下面這種的拋物線的動畫。

如果要實現這種平拋效果,需要一點基礎的高中物理知識。

平拋運動由水平方向的兩種運動合成而得到

  • 水平方向: 勻速直線運動
  • 垂直方向:初速度為0的勻加速直線運動

如下所示

如果我們通過圖像捕捉的方式就可理解的更清楚了,從下面的圖可以看到:

水平方向的速度是不變的,而垂直方向的速度是不斷加快的

好,下面終於可以講下CSS的實現思路了

CSS實現原理

  1. 設置兩個div:外層div和內層div
  2. 外層div設置橫向勻速運動的動畫
  3. 內層div設置縱向的勻加速直線運動的動畫,加速過程可以用cubic-bezier設置

cubic-bezier又叫做貝塞爾曲線,它可接收四個參數,來規定動畫的速度變化過程,使用方法如下

transition-timing-function: cubic-bezier(0.1, 0.7, 1.0, 0.1);

我們可以通過下面這個官方網站去設置速度變化曲線,然後獲取生成的四個參數

https://cubic-bezier.com/

具體代碼如下:

 // HTML  <div id="outer">    <div id="inner"></div>  </div>  <button id='btn'>拋物線效果</button>    // CSS  #outer {    transition: all 1.5s linear;  }    #inner {    width: 30px;    height: 30px;    border-radius: 50%;    background: red;    transition: all 1.5s cubic-bezier(.54, .11, .95, .68);  }    .outer-active {    transform: translateX(300px);  }    .inner-active {    transform: translateY(300px) scale(0.3);  }
JS
document.getElementById("btn").onclick = function() {    document.getElementById("outer").classList.add("outer-active");    document.getElementById("inner").classList.add("inner-active");  };

效果如下

無限滾動動畫—實現跑馬燈效果

當文本過長時候,我們可能需要做成跑馬燈效果,然後無限滾動播放。

因為marquee這個HTML元素被廢棄了,所以一般情況下我們需要手動通過動畫去實現跑馬燈

實現圖示如下,注意開始位置和結束位置是不可見的

// HTML  <div class="marquee">    <p>ABCDEFGHIJKLMN</p>  </div>  // CSS  @keyframes marquee {    from {      transform: translateX(-200px)    }      to {      transform: translateX(200px)    }  }    .marquee {    overflow: hidden;    margin: 100px;    width: 200px;  }    .marquee p {    animation: marquee 3s linear infinite;  }

結果

perspective和transform的運用——實現卡片翻轉

卡片翻轉三要素

  • transform: rotateY(x deg) 翻轉卡片
  • backface-visibility:hidden 翻轉後隱藏背面,重要!必須要加
  • perspective:增加透視和立體效果
// HTML  <div id="img-wrapper">    <img src='./timg.jpg' id='img1' class="img disable-img1" />    <img src='./timg2.jpg' id='img2' class="img" />  </div>    // CSS  #img-wrapper {    perspective: 1200px;    position: relative;    height: 479px;  }    #img1,  #img2 {    position: absolute;    transition: all 1s linear;    backface-visibility: hidden;  }    #img1 {    transform: rotateY(-180deg);  }    #img-wrapper:hover #img1 {    transform: rotateY(-360deg);  }    #img-wrapper:hover #img2 {    transform: rotateY(-180deg);  }

結果