阅读 156

SVG 立方体内嵌路径拼接

1.前言

我使用了jquery编写交互的旋转,因为初学所以不太熟练,还请见谅。样式我是写stylus编码,自动生成css。


2.正文

话不多说,先上一张效果图。

我将其分成三部分。第一部分是正方体部分(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…

关注下面的标签,发现更多相似文章
评论