SVG 立方體內嵌路徑拼接

熊文俊發表於2019-04-06

1.前言

我使用了jquery編寫互動的旋轉,因為初學所以不太熟練,還請見諒。樣式我是寫stylus編碼,自動生成css。


2.正文

話不多說,先上一張效果圖。

SVG 立方體內嵌路徑拼接

我將其分成三部分。第一部分是正方體部分(SVG),第二部分是svg中的路徑動畫了(animateMotion + jQuery),第三部分則是互動旋轉(jQuery)。

1.正方體

做一個正方體

我的思路是用六塊svg正方形畫板通過css屬性旋轉和平移來構成正方體。

html程式碼:

<div class="page">
    <div class="state">
        <!-- 定位-->
        <div class="container">
            <!--旋轉-->
            <!-- 前 -->
            <svg  xmlns="http://www.w3/org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
                class="front">
                <rect class="rect"></rect>
            </svg> 
            <!-- 後 --> 
            <svg  xmlns="http://www.w3/org/2000/svg"
                xmlns:xlink="http://www.w3.org/1999/xlink" class="behind">
                <rect class="rect "></rect>
            </svg> 
            <!-- 左 --> 
            <svg  xmlns="http://www.w3/org/2000/svg"
                xmlns:xlink="http://www.w3.org/1999/xlink" class="left">
                <rect class="rect "></rect>
            </svg> 
            <!-- 右 --> 
            <svg  xmlns="http://www.w3/org/2000/svg"
                xmlns:xlink="http://www.w3.org/1999/xlink" class="right">
                <rect class="rect "></rect>
            </svg> 
            <!-- 上 --> 
            <svg xmlns="http://www.w3/org/2000/svg"
                xmlns:xlink="http://www.w3.org/1999/xlink" class="top">
                <rect class="rect "></rect>
            </svg> 
            <!-- 下 --> 
            <svg  xmlns="http://www.w3/org/2000/svg"
                xmlns:xlink="http://www.w3.org/1999/xlink" class="bottom">
                <rect class="rect "></rect>
            </svg> </div>
    </div>
</div>
複製程式碼

stylus程式碼:

html
    margin 0
    padding 0
    height 100%
    width 100%
    body
        height 100%
        width 100%
        .page//頁面
            .state//用於定位的盒子
                height 300px
                width 300px
                position absolute
                top 50%
                left 50%
                transform translate(-50%,-50%)//修正位置
                .container//放置六個svg平面旋轉平移後,可視作為立方體
                    transform-style preserve-3d
                    position relative
                    height 300px
                    width 300px
                    transform rotateX(0deg) rotateY(0deg)
                    svg
                        position absolute
                        height 300px
                        width 300px
                        stroke-width 5px
                        stroke brown
                        fill transparent //填充用透明
                        .rect
                            height 300px
                            width 300px
                    .top
                        transform rotateX(90deg) translateZ(150px)
                    .bottom 
                        transform rotateX(-90deg) translateZ(150px)
                    .left
                        transform rotateY(-90deg) translateZ(150px)
                    .right
                        transform rotateY(90deg) translateZ(150px)
                    .front
                        transform rotateY(0deg) translateZ(150px)
                    .behind
                        transform rotateY(180deg) translateZ(150px)
複製程式碼

通常有兩種方式做成立方體。

第一種:先平移再旋轉。優點是不需太強的空間構造能力,寫起來比較簡單。缺點就是:程式碼數會多一些,需要在平移後設定旋轉基點。樣式附在下面替換一下即可。

.top    
    fill blue    
    transform-origin: bottom    
    transform translateY(-200px) rotateX(90deg)
.bottom     
    fill red    
    transform-origin: top    
    transform translateY(200px) rotateX(-90deg)
.left    
    fill green    
    transform-origin: right    
    transform translateX(-200px) rotateY(-90deg)
.right    
    fill black    
    transform-origin: left    
    transform translateX(200px) rotateY(90deg)
.front    
    fill grey    
    transform translateZ()
.behind    
    fill pink    
    transform translateZ(-200px)
複製程式碼

第二種:先旋轉再平移。這個的特點就是與上面的相反了。(我使用的是這種)

兩種方式都能生成正方體。

兩種正方體生成的原理圖

第一種

平移後的狀況

先平移的方式

第二種

旋轉後的狀況

先旋轉的方式

以上就是兩種立方體的構成方法的概念了。


2. 路徑動畫

先上程式碼:

比較冗雜

<div class="state">
    <!-- 定位-->
    <div class="container">
        <!--旋轉-->
        <!-- 前 --> 
        <svg  xmlns="http://www.w3/org/2000/svg"
            xmlns:xlink="http://www.w3.org/1999/xlink" class="front">
            <rect class="rect"></rect>
            <circle x="0" y="0" r="5" stroke="none" fill="red">
                <animateMOtion dur="3s" begin="infinite">
                    <mpath xlink:href="#frontY"></mpath>
                </animateMOtion>
            </circle>
            <circle x="0" y="0" r="5" stroke="none" fill="blue">
                <animateMOtion dur="3s" begin="infinite">
                    <mpath xlink:href="#frontX"></mpath>
                </animateMOtion>
            </circle>
            <path fill="#fff" stroke="#000" stroke-width="1.5" stroke-opacity="null" fill-opacity="null" opacity="0.5"
                d="m150.75,0.75l0,300" id="frontY" />
            <path fill="#fff" stroke="#000" stroke-width="1.5" stroke-opacity="null" fill-opacity="null" opacity="0.5"
                d="m300,150c-300,0 -300,0 -300,0" id="frontX" />
        </svg> 
        <!-- 後 --> 
        <svg  xmlns="http://www.w3/org/2000/svg"
            xmlns:xlink="http://www.w3.org/1999/xlink" class="behind">
            <rect class="rect "></rect>
            <circle x="0" y="0" r="5" stroke="none" fill="red">
                <animateMOtion dur="3s" begin="infinite">
                    <mpath xlink:href="#behindY"></mpath>
                </animateMOtion>
            </circle>
            <circle x="0" y="0" r="5" stroke="none" fill="blue">
                <animateMOtion dur="3s" begin="infinite">
                    <mpath xlink:href="#behindX"></mpath>
                </animateMOtion>
            </circle>
            <path fill="#fff" stroke="#000" stroke-width="1.5" stroke-opacity="null" fill-opacity="null" opacity="0.5"
                d="m150,300c0,0 1,-300 0,-300" id="behindY" />
            <path fill="#fff" stroke="#000" stroke-width="1.5" stroke-opacity="null" fill-opacity="null" opacity="0.5"
                d="m300,150c-300,0 -300,0 -300,0" id="behindX" />
        </svg> 
        <!-- 左 --> 
        <svg  xmlns="http://www.w3/org/2000/svg"
            xmlns:xlink="http://www.w3.org/1999/xlink" class="left">
            <rect class="rect "></rect>
            <circle x="0" y="0" r="5" stroke="none" fill="blue">
                <animateMOtion dur="3s" begin="infinite">
                    <mpath xlink:href="#leftX"></mpath>
                </animateMOtion>
            </circle>
            <path fill="#fff" stroke="#000" stroke-width="1.5" stroke-opacity="null" fill-opacity="null" opacity="0.5"
                d="m300,150c-300,0 -300,0 -300,0" id="leftX" />
        </svg> 
        <!-- 右 --> 
        <svg  xmlns="http://www.w3/org/2000/svg"
            xmlns:xlink="http://www.w3.org/1999/xlink" class="right">
            <rect class="rect "></rect>
            <circle x="0" y="0" r="5" stroke="none" fill="blue">
                <animateMOtion dur="3s" begin="infinite">
                    <mpath xlink:href="#rightX"></mpath>
                </animateMOtion>
            </circle>
            <path fill="#fff" stroke="#000" stroke-width="1.5" stroke-opacity="null" fill-opacity="null" opacity="0.5"
                d="m300,150c-300,0 -300,0 -300,0" id="rightX" />
        </svg> 
        <!-- 上 --> 
        <svg  xmlns="http://www.w3/org/2000/svg"
            xmlns:xlink="http://www.w3.org/1999/xlink" class="top">
            <rect class="rect "></rect>
            <circle x="0" y="0" r="5" stroke="none" fill="red">
                <animateMOtion dur="3s" begin="infinite">
                    <mpath xlink:href="#topY"></mpath>
                </animateMOtion>
            </circle>
            <path fill="#fff" stroke="#000" stroke-width="1.5" stroke-opacity="null" fill-opacity="null" opacity="0.5"
                d="m150.75,0.75l0,300" id="topY" />
        </svg> 
        <!-- 下 --> 
        <svg  xmlns="http://www.w3/org/2000/svg"
            xmlns:xlink="http://www.w3.org/1999/xlink" class="bottom">
            <rect class="rect "></rect>
            <circle x="0" y="0" r="5" stroke="none" fill="red">
                <animateMOtion dur="3s" begin="infinite">
                    <mpath xlink:href="#bottomY"></mpath>
                </animateMOtion>
            </circle>
            <path fill="#fff" stroke="#000" stroke-width="1.5" stroke-opacity="null" fill-opacity="null" opacity="0.5"
                d="m150.75,0.75l0,300" id="bottomY" />
        </svg> 
    </div>
</div>
複製程式碼

路徑動畫則是由animateMotion元素來做的。

首先每個svg中圓點的路徑是path動畫,在這裡我用了直線,也可以換成別的路徑,只要第一個路徑的結束部分和第二個路徑的起始部分接近重合就可以做到看著像連線到一起的動畫。

路徑我是用Method Draw畫的 editor.method.ac/

有興趣的可以自己設計一下

接下來就是重點的路徑控制

$(document).ready(function () {
    const animate = document.getElementsByTagName('animateMotion');
    // 0 frontY 1 frontX 2 behindY 3 behindX 4 leftX 5 rightX 6 topY 7 bottomY
    var frontY = animate[0], frontX = animate[1],
        behindY = animate[2], behindX = animate[3],
        leftX = animate[4], rightX = animate[5],
        topY = animate[6], bottomY = animate[7];
    // Y面球體運動
    (() => {
        //先執行一次
        frontY.beginElement();
        setTimeout(() => {
            bottomY.beginElement();
        }, 3000);
        setTimeout(() => {
            behindY.beginElement();
        }, 6000);
        setTimeout(() => {
            topY.beginElement();
        }, 9000);
        // 迴圈執行動畫
        var time = setInterval(() => {
            frontY.beginElement();
            setTimeout(() => {
                bottomY.beginElement();
            }, 3000);
            setTimeout(() => {
                behindY.beginElement();
            }, 6000);
            setTimeout(() => {
                topY.beginElement();
            }, 9000);
        }, 12000);
    })();
    // X面球體運動
    (() => {
        //先執行一次
        frontX.beginElement();
        setTimeout(() => {
            leftX.beginElement();
        }, 3000);
        setTimeout(() => {
            behindX.beginElement();
        }, 6000);
        setTimeout(() => {
            rightX.beginElement();
        }, 9000);
        // 迴圈執行動畫
        var time = setInterval(() => {
            frontX.beginElement();
            setTimeout(() => {
                leftX.beginElement();
            }, 3000);
            setTimeout(() => {
                behindX.beginElement();
            }, 6000);
            setTimeout(() => {
                rightX.beginElement();
            }, 9000);
        }, 12000);
    })();
})
複製程式碼

我的控制方式是全部由jQuery來控制,animateMotion元素中設定的起始時間begin屬性為infinite這是在頁面接在完後不會自己執行的

我使用jQuery來控制動畫的開始。

首先 先獲取每個動畫元素 const animate = document.getElementsByTagName('animateMotion'); 將每個動畫元素都標記好是什麼動畫。

接著 我在這使用了計時器setIntervalsetTimeout來控制動畫。 用setInterval來迴圈播放動畫,再每次迴圈中分別用setTimeout來控制動畫的的先後順序。 每個setTimeout計時器的延遲等於之前所有動畫的總時間,可以獲取元素的dur等方法獲取和設定,在這為圖方便設定了固定值。有興趣的可以設定一個動畫陣列,裡面按序新增animateMotion動畫元素。每個setTimeout計時器的延時設定為之前動畫的時間之和即可。

最後有兩個方法可以控制動畫的停止與繼續,是svg內建的API

如果要使用這兩個API的話,最好將動畫的begin值設定為上一個動畫.endbegin的值支援很多型別,詳情請看張鑫旭大佬的文章

www.zhangxinxu.com/wordpress/2…

// svg指當前svg DOM元素
// 暫停
svg.pauseAnimations();
// 重啟動
svg.unpauseAnimations()
複製程式碼

3.旋轉控制

旋轉控制是我還沒完善的地方,體驗不是十分好,還望大佬們幫我指出錯誤。另一個旋轉方案過一兩天再新增上來

先上程式碼:

var X = 0;//記錄X軸旋轉過的角度
var Y = 0;//記錄Y軸旋轉過的角度
// 旋轉控制
$('.container').mousedown(function (event) {
    var mousedownX = event.clientX;
    var mousedownY = event.clientY;
    $('body').mousemove(function (event) {
        var mousemoveX = event.clientX;
        var mousemoveY = event.clientY;
        var scaleY = ((mousemoveX - mousedownX) / 200);
        var scaleX = ((mousemoveY - mousedownY) / 200);
        Y = ((Y + scaleY) % 360);
        X = ((X + scaleX) % 360);
        $('.container').animate({}, function () {
            $('.container').css({ 'transform': `rotateX(${X}deg) rotateY(${Y}deg)` });
        })
    })
})
$('body').mouseup(function () {
    $('body').unbind('mousemove');
    $('body').unbind('mousedown');
})
複製程式碼

旋轉事件由在立方體上滑鼠按下事件觸發啟動,觸發後由在頁面上滑鼠移動來觸發旋轉,結束則由在頁面上滑鼠回彈觸發移除滑鼠旋轉事件。

首先在旋轉控制的函式前需要儲存上次旋轉的角度所以設定了X``Y的變數。

接著在立方體元素中新增mousedown()事件,在mousedown的回撥函式中先記錄下滑鼠點選的位置 var mousemoveX = event.clientX; 並且在body元素上新增mousemove()事件因為是要在整個頁面上移動。

最後就是最重要的移動部分,先記錄滑鼠移動的位置 var mousemoveX = event.clientX; 然後計算滑鼠移動的距離var scaleY = ((mousemoveX - mousedownX) / 200);這裡面200是可隨意更改的,因為滑鼠移動距離對於旋轉角度來說太大了所以要除以一個倍率可以自己來設定,其次是計算正方體對於初始的位置旋轉的多少角度 Y = ((Y + scaleY) % 360);這裡要除以360做範圍限制,其實不新增也可以,rotate屬性支援超過360度。接著就是設定旋轉角度了

$('.container').animate({}, function () {
    $('.container').css({ 'transform': `rotateX(${X}deg) rotateY(${Y}deg)` });
})
複製程式碼

通過animate()方法來將旋轉做動畫效果在裡邊通過css()方法來設定旋轉的角度。

第二種方案: 第二種方案是不需要使用者點選,實時監聽滑鼠移動,滑鼠移動就會旋轉。我將旋轉方向判斷的基點設定為螢幕的中點。 上程式碼

var HalfX=window.innerWidth;
    var HalfY=window.innerHeight;
    var mousemoveX = null;
    var mousemoveY = null;
    window.addEventListener('resize',onchange);
    function onchange(){
        var HalfX=window.innerWidth;
        var HalfY=window.innerHeight;
    }
    $('body').mousemove((event)=>{
        mousemoveX = ((event.clientX - HalfX))%360;
        mousemoveY = ((event.clientY - HalfY))%360;
        $('.container').animate({},function(){
        $('.container').css('transform',`rotateX(${mousemoveY}deg)rotateY(${mousemoveX}deg)`);
        })
    })
複製程式碼

方案的更改沒有太大的差別。只是觸發的事件不同和滑鼠移動距離計算的基點不同。 在第二個方案中我們不需要儲存上一次的旋轉的角度。因為我們相對的基點是絕對的,角度的更改不會有相對座標系的問題,每一個位置相對於立方體旋轉的角度是固定的,不過我們也需要記錄下來基點的值,當螢幕改變時我們的基點也需要改變onchange()方法。


總結

最後將github庫連結放上來

github.com/0xiongwenju…

相關文章