讓小球作圓周運動,你有幾種辦法?

最近在閱讀外國技術文章中無心中發現了一個神奇的CSS屬性motion-path,它可讓Dom元素能夠按照自定義的路徑移動。javascript

又想起了好久以前參加校招面試的時候,面試官問了我一個問題「能不能不借助庫實現小球在瀏覽器中作圓周運動?」,因而就整理了一下讓小球圓周運動的方法(純屬無聊不喜勿噴)。css

由於以前的題目過於簡單,做爲一個需求來講缺乏了一些必要條件,因而,咱們給它增長一些條件,讓他看起來更靠譜些。html

在瀏覽器中間600px*600px的指定區域內,不借助任何第三方插件,利用原生JavaScript或者CSS讓一個半徑爲25px的小球圍繞指定區域的中心作圓周運行,你有幾種方法?前端

在讓小球進行圓周運動以前,讓咱們先來實現一下題目中提到的基礎樣式,劇中的容器半徑25px的小球java

.container {
    position: absolute;
    left: 50%;
    top: 50%;
    margin: -300px 0 0 -300px;
    width: 600px;
    height: 600px;
    background: lightgray;
}

.ball {
    position: relative;
    width: 50px;
    height: 50px;
    border-radius: 50%;
    background-color: coral;
}
複製代碼

基礎樣式很容易就完成了,咱們再來思考一下實現圓周運動的具體方法。咱們能夠將解決方法分爲CSS和JavaScript兩大類。面試

CSS方法

方法1: 雙Dom元素,父元素使用animation屬性

<div class="container">
    <div class="inner-container">
        <div class="ball"></div>
    </div>
</div>
複製代碼
.inner-container {
    width: 600px;
    height: 600px;
    animation: rotate 8s linear infinite;
}

.ball {
    margin: 0 auto;
}

@keyframes rotate {
    to {
        transform: rotate(1turn);
    }
}
複製代碼

上面的方法屬於很容易想到的一種方法,這裏咱們利用了animation屬性,一直循環播放定義好的keyframe動畫rotate便可。chrome

僅僅經過animationtransform屬性讓外層元素旋轉起來,視覺效果上看起來就是小圓球在旋轉。爲了讓效果明顯,咱們把外層元素的背景色弄的明顯一些。canvas

方法2: 單Dom元素,修改transform-origin

若是是單個Dom元素,咱們怎麼才能讓小球作圓周運動呢。以下圖,A點圍繞O點移動必定的角度到達B點,咱們不斷擴大轉動的角度便可實現圓周運動。瀏覽器

咱們只須要讓咱們的圓圍繞着某一箇中心點旋轉就能夠了。而CSS中恰好提供了這樣一個屬性transform-origin,讓咱們可以修改DOM節點的中心點。svg

在咱們的基礎樣式中,在.ball上增長transform-origin: 300px center;屬性,就能夠幫咱們把旋轉的圓心向右移動300px(圖中紅色區域)。

再經過上面實現的旋轉動畫,便可實現圓周運動。具體實現代碼以下:

<div class="container">
    <div class="ball"></div>
</div>
複製代碼
.ball {
    top: 50%;
    margin-top: -25px;
    transform-origin: 300px center;
    animation: rotate 8s linear infinite;
}

@keyframes rotate {
    to {
        transform: rotate(1turn);
    }
}
複製代碼

方法3: motion path

前面兩個方法相對比較常見,咱們的第三種方法將會用到前面提到css屬性motion path

咱們的題目中是在圓周運動,所以,還能夠單純經過animationtransform屬性來完成效果,若是咱們的題目變動成了要去實現一條複雜的曲線就會很僵硬,好比下面的路徑。

爲了解決上面的問題,2015年W3C提出了CSS路徑動畫相關的草案,也就是咱們如今準備要使用的motion path。其中包括了5個屬性:

  • offset-path:運動路徑
  • offset-anchor:運動元素的錨點位置
  • offset-position:定了路徑自己的初始位置
  • offset-distance:運動元素在路徑上的位置
  • offset-rotate:對象的旋轉角度或是如何自動旋轉

其中,offset-path接收path()url()ray()none等值。這裏的path()是使用SVG座標語法定義的路徑字符串。那咱們的問題就變得簡單了,只須要找到在svg中如何實現圓環路徑便可。

下面是咱們找到的SVG中實現一個圓環路徑的代碼,其中cxcy表明圓形的座標, r表明圓的半徑,填入對應的值便可生成咱們想要的圓環路徑M 300, 300 m -275, 0 a 275,275 0 1,0 550,0 a 275,275 0 1,0 -550,0

<path d="
    M cx cy
    m -r, 0
    a r,r 0 1,0 (r * 2),0
    a r,r 0 1,0 -(r * 2),0
"/>
複製代碼

在實現完軌跡以後,咱們還須要經過控制offset-distance的值讓咱們的圓在咱們畫出來的圓環路徑上動起來。

具體實現代碼以下:

<div class="container">
    <div class="ball"></div>
</div>
複製代碼
.ball {
    offset-path: path('M 300, 300 m -275, 0 a 275,275 0 1,0 550,0 a 275,275 0 1,0 -550,0');
    animation: move 3000ms linear infinite;
}

@keyframes move {
    0% {
        offset-distance: 0%;
    }
    100% {
        offset-distance: 100%;
    }
}
複製代碼

Javascript方法

方法4: requestAnimationFrame + Dom

咱們來到了JavaScript環節,經過萬能的JS咱們只須要掌握一點點數學知識和定時器知識便可實現咱們的小球運動啦~

這裏實現時有2個須要注意的點:

  • 實現動畫播放時,爲何用requestAnimationFrame不用setInterval?
  • 改變圓的位置時,爲何不直接修改topleft,而是要修改transform?

問題1: 實現動畫播放時,爲何用requestAnimationFrame不用setInterval?

爲了保證動畫運行流暢性,咱們須要儘可能保持動畫的播放幀率大於等於60Hz,即1000 / 60 = 16.67ms播放一次動畫,requestAnimationFramesetInterval均可以控制動畫渲染的間隔。

可是,setInterval自己是基於時間來設置,它僅僅是將須要執行的事件按期插入到須要執行的隊列中,無論,前面的事件是否已經執行了。所以,可能存在屢次定時器之間的間隔可能小於咱們設定的值,致使動畫執行過快或者直接跳幀

requestAnimationFrame則是告知瀏覽器在刷新下一幀時須要執行那些函數。確保咱們的動畫的播放幀率和屏幕的刷新幀率一致。

問題2: 改變圓的位置時,爲何不直接修改topleft,而是要修改transform?

直接修改topleft會觸發瀏覽器的重排和重繪,而修改tranform僅僅會觸發重繪。而頻繁的重排對動畫性能影響極大,具體緣由你們能夠百度一下瀏覽器 重排 重繪,相關的文章很是多。

另外想要查看本身的頁面是否觸發了重排、重繪,你們能夠藉助chromerendering面板來查看

說了這麼多,咱們來看一下使用requestAnimationFrame怎麼實現圓周運動的動畫,具體實現代碼以下:

<div class="container">
    <div id="ball" class="ball"></div>
</div>
複製代碼
let $ball = document.querySelector('#ball');
let angle = 0;
let getPosition = ((centerX, centerY, radius) => {
    return (angle) => {
        return {
            x: +centerX + Math.cos(angle) * radius,
            y: +centerY + Math.sin(angle) * radius,
        };
    };
})(275, 275, 275);

run();

function run() {
    // 角度加1
    angle += 1;
    let position = getPosition((angle * Math.PI) / 180);
    $ball.style.transform = `translate(${position.x}px, ${position.y}px)`;
    window.requestAnimationFrame(run);
}
複製代碼

方法5: requestAnimationFrame + canvas

除了經過Dom來控制元素以外,咱們還能夠經過canvas直接在頁面上畫圓。canvas作動畫的原理就是快速的重複「擦除->移動位置畫圓」的這個步驟。這裏就不解釋爲啥了,直接上代碼

具體實現代碼以下:

<div class="container">
    <canvas id="canvas" width="600px" height="600px"></canvas>
</div>
複製代碼
let angle = 0;
let getPosition = ((centerX, centerY, radius) => {
    return (angle) => {
        return {
            x: +centerX + Math.cos(angle) * radius,
            y: +centerY + Math.sin(angle) * radius,
        };
    };
})(300, 300, 275);

let $canvas = document.querySelector('#canvas');
let ctx = $canvas.getContext('2d');
// 設置填充色
ctx.fillStyle = 'coral';

run();

function run() {
    angle += 1;
    let position = getPosition((angle * Math.PI) / 180);

    //清空畫布
    ctx.clearRect(0, 0, 600, 600);
    ctx.beginPath();
    // 畫圓
    ctx.arc(position.x, position.y, 25, 0, 2 * Math.PI, true);
    //填充
    ctx.fill();
    ctx.closePath();

    window.requestAnimationFrame(run);
}
複製代碼

好啦,打完收工,這裏就是我能想到的讓小球作圓周運動的5種方法。

若是還有別的方法能夠留言交流一下,一塊兒漲漲姿式。

最近,搞了一個公衆號,你們有興趣的話關注一下,咱們一塊兒交流前端知識~

相關文章
相關標籤/搜索