随机生成千山万水动画

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <title>千山万水</title>
        <style>
            body {
                margin: 0;
                padding: 0;
            }
            #container {
                margin: 0;
                padding: 0;
                position:absolute;
                top:0;
                left:0;
                width:100%;
                height:100vh;
                overflow:hidden;
                background-color: #fff;
                box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
                background-color:#ff3333;
            }
            canvas {                
                margin: 0;
                padding: 0;
                position:absolute;
                top:0;
                left:-100px;
            }
            svg{
                margin: 0;
                padding: 0;
                position:absolute;
                top:0;
                left:0;
            }
        </style>
    </head>
    <body>
        <!-- 千山万水 -->
        
        <div id="container"></div>

        
    </body>
    <script>
        // 使用SVG绘制山脉

        // 画布高度和页面高度相同
        let canvasHeight=window.innerHeight;
        console.log(`canvasHeight=${canvasHeight}`);

        //----------------------------       

        function generateMountainsCurvePoints (pointCount=9,mtWidthBase=30,mtWidthVariation=120,mtHeightBase=100,mtHeightVariation=200) {
            // 生成随机的山脉曲线点
            // 山脉曲线点数组
            let mountainCurvePoints=[];
            
            // 起点
            let currentX=0;
            let currentY=canvasHeight;

            // 随机生成山脉曲线点
            for (let i = 0; i < pointCount; i++) {
                mountainCurvePoints.push([currentX,currentY]);
                //生成随机偏移量
                //x偏移量限定区间100-200
                let xOffset=Math.random()*mtWidthVariation+mtWidthBase;
                currentX+=xOffset;
                //y值随机变幻区间
                currentY=Math.random()*mtHeightVariation+mtHeightBase;
                currentY=canvasHeight-currentY;
            }             
            // 终点
            mountainCurvePoints.push([currentX+100,canvasHeight]);
            // 返回结果数组
            return mountainCurvePoints;
        }
        //----------------------------
        // 绘制山脉
        function drawMountains (canvasId,mountainCurvePoints,mtLineColor='rgb(255,0,0,1)',mtFillColor='rgb(255,0,0,1)',mtLineWidth=1) {
            console.log(canvasId);
            // 获取Canvas元素
            const canvas = document.getElementById(canvasId);
            const ctx = canvas.getContext('2d');
            // 设置Canvas布大小mountainCurvePoints最后一个点的x坐标决定
            // 画布宽度由mountainCurvePoints最后一个点的x坐标决定
            canvas.width = mountainCurvePoints[mountainCurvePoints.length-1][0];
            console.log(`canvas.width=${canvas.width}`);
            canvas.height = canvasHeight;
            // 设置背景颜色
            ctx.fillStyle='rgba(255,0,0,0)';
            ctx.fillRect(0,0,canvas.width,canvas.height);

            
            // 绘制山脉曲线,使用随机颜色
            ctx.strokeStyle=`${mtLineColor}`;
            ctx.lineWidth=mtLineWidth;
            ctx.lineCap='round';
            ctx.lineJoin='round';

            ctx.beginPath();
            ctx.moveTo(mountainCurvePoints[0][0], mountainCurvePoints[0][1]);
            for (let i = 0; i < mountainCurvePoints.length-1; i++) {
                // ctx.lineTo(mountainCurvePoints[i][0], mountainCurvePoints[i][1]);
                //绘制圆滑曲线
                const p0x=mountainCurvePoints[Math.max(i-1,0)][0];
                const p0y=mountainCurvePoints[Math.max(i-1,0)][1];

                const p1x=mountainCurvePoints[i][0];
                const p1y=mountainCurvePoints[i][1];

                const p2x=mountainCurvePoints[i+1][0];
                const p2y=mountainCurvePoints[i+1][1];

                const p3x=mountainCurvePoints[Math.min(i+2,mountainCurvePoints.length-1)][0];
                const p3y=mountainCurvePoints[Math.min(i+2,mountainCurvePoints.length-1)][1];

                //计算控制点
                const tension=0.15;
                const cp1x=p1x+(p2x-p0x)*tension;
                const cp1y=p1y+(p2y-p0y)*tension;
                const cp2x=p1x+(p3x-p1x)*tension;
                const cp2y=p1y+(p3y-p1y)*tension;

                ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2x, p2y);

            }

            //闭合曲线
            ctx.closePath();        
            ctx.stroke();
            //填充颜色
            ctx.fillStyle=`${mtFillColor}`;
            ctx.fill();
        }
        //----------------------------
        function drawMountainsSVG (canvasId,mountainCurvePoints,mtLineColor='rgb(255,0,0,1)',mtFillColor='rgb(255,0,0,1)',mtLineWidth=1) {
            // 绘制山脉
            //创建SVG元素svg
            const svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
            // 设置SVG元素的属性
            svg.setAttribute('width',mountainCurvePoints[mountainCurvePoints.length-1][0]);
            svg.setAttribute('height',canvasHeight);
            svg.setAttribute('id',canvasId);
            // 设置SVG元素的属性
            svg.setAttribute('viewBox',`0 0 ${mountainCurvePoints[mountainCurvePoints.length-1][0]} ${canvasHeight}`);
            // 创建path元素
            const path = document.createElementNS('http://www.w3.org/2000/svg','path');
            // 构造path文本字符串
            let pathText=`M ${mountainCurvePoints[0][0]} ${mountainCurvePoints[0][1]} `;
            // 按照mountainCurvePoints的坐标点,绘制山脉曲线
            for (let i = 0; i < mountainCurvePoints.length; i++) {
                //构造path文本字符串
                pathText+=`L ${mountainCurvePoints[i][0]} ${mountainCurvePoints[i][1]} `;
            }
            // 闭合曲线
            pathText+='Z';
            // 设置path元素的属性
            path.setAttribute('d',pathText);
            // 绘制山脉曲线,使用随机颜色
            path.setAttribute('stroke',`${mtLineColor}`);
            path.setAttribute('stroke-width',`${mtLineWidth}`);
            path.setAttribute('stroke-linecap','round');
            path.setAttribute('stroke-linejoin','round');
            //添加linearGradient线性渐变填充
            const linearGradient  = document.createElementNS('http://www.w3.org/2000/svg','linearGradient');
            linearGradient.setAttribute('id',`linearGradientFill${canvasId}`);
            linearGradient.setAttribute('x1','0%');
            linearGradient.setAttribute('y1','0%');
            linearGradient.setAttribute('x2','0%');
            linearGradient.setAttribute('y2','100%');
            const stop1  = document.createElementNS('http://www.w3.org/2000/svg','stop');
            stop1.setAttribute('stop-color',`${mtFillColor}`);
            stop1.setAttribute('offset','0%'); 
            linearGradient.appendChild(stop1);
            const stop2  = document.createElementNS('http://www.w3.org/2000/svg','stop');
            stop2.setAttribute('stop-color',`orange`);
            stop2.setAttribute('offset','100%'); 
            linearGradient.appendChild(stop2);
            // 添加linearGradient元素到svg元素
            svg.appendChild(linearGradient);


            path.setAttribute('fill',`url(#linearGradientFill${canvasId})`);
            // 填充颜色
            // path.setAttribute('fill',`${mtFillColor}`);
            // 添加path元素到svg元素
            svg.appendChild(path);
            // 添加svg元素到container元素
            document.getElementById('container').appendChild(svg);

            
        }
        //----------------------------
        // 动画:canvas水平平移动画循环
        function moveMt(canvasId,speed=10,stopGate=1000000) {
            // 获取Canvas元素
            let mt = document.getElementById(canvasId);
            // 通过修改left属性实现水平平移动,left的属性无法直接获取,需要通过getComputedStyle()方法获取当前left值,,
            // 所能直接修改left属性,实现水平平移动
            let mtLeft=parseInt(getComputedStyle(mt).left);
            // console.log("canvas left:"+mtLeft);
            mt.style.left=mtLeft-speed+"px";
            //动画循环
            let animationId=requestAnimationFrame(()=>{
                    moveMt(canvasId,speed);
                });
            // 触发结束循环的条件是left绝对值大于等于当前画布的宽度

            // console.log(`stopGate=${stopGate}`);
            if(mtLeft<=-stopGate){
                console.log(`${canvasId} 结束循环`);           
                cancelAnimationFrame(animationId);  
            }

        }
        //----------------------------
        // 绘制山脉
        let pointCount=10000;
        let mtWidthVariation=50;
        let mtHeightVariation=145;
        mtps0=generateMountainsCurvePoints(pointCount=pointCount,mtWidthBase=30,mtWidthVariation=mtWidthVariation,mtHeightBase=870,mtHeightVariation=mtHeightVariation);
        mtps1=generateMountainsCurvePoints(pointCount=pointCount,mtWidthBase=30,mtWidthVariation=mtWidthVariation,mtHeightBase=800,mtHeightVariation=mtHeightVariation);
        mtps2=generateMountainsCurvePoints(pointCount=pointCount,mtWidthBase=30,mtWidthVariation=mtWidthVariation,mtHeightBase=720,mtHeightVariation=mtHeightVariation);
        mtps3=generateMountainsCurvePoints(pointCount=pointCount,mtWidthBase=30,mtWidthVariation=mtWidthVariation,mtHeightBase=630,mtHeightVariation=mtHeightVariation);
        mtps4=generateMountainsCurvePoints(pointCount=pointCount,mtWidthBase=30,mtWidthVariation=mtWidthVariation,mtHeightBase=530,mtHeightVariation=mtHeightVariation);
        mtps5=generateMountainsCurvePoints(pointCount=pointCount,mtWidthBase=30,mtWidthVariation=mtWidthVariation,mtHeightBase=420,mtHeightVariation=mtHeightVariation);
        mtps6=generateMountainsCurvePoints(pointCount=pointCount,mtWidthBase=30,mtWidthVariation=mtWidthVariation,mtHeightBase=320,mtHeightVariation=mtHeightVariation);
        mtps7=generateMountainsCurvePoints(pointCount=pointCount,mtWidthBase=30,mtWidthVariation=mtWidthVariation,mtHeightBase=200,mtHeightVariation=mtHeightVariation);

        //----------------------------
        //SVG模式绘制山脉
        let mtFillColorList=['#ff4444','#ff5555','#ff6666','#ff7777','#ff8888','#ff9999','#ffaaaa','#ffbbbb','#ffcccc','#ffdddd','#ffeeee','#ffffff'];
        drawMountainsSVG("canvas0",mtps0,mtLineColor=mtFillColorList[0],mtFillColor=mtFillColorList[0],mtLineWidth=0);
        drawMountainsSVG("canvas1",mtps1,mtLineColor=mtFillColorList[1],mtFillColor=mtFillColorList[1],mtLineWidth=0);
        drawMountainsSVG("canvas2",mtps2,mtLineColor=mtFillColorList[2],mtFillColor=mtFillColorList[2],mtLineWidth=0);
        drawMountainsSVG("canvas3",mtps3,mtLineColor=mtFillColorList[3],mtFillColor=mtFillColorList[3],mtLineWidth=0);
        drawMountainsSVG("canvas4",mtps4,mtLineColor=mtFillColorList[4],mtFillColor=mtFillColorList[4],mtLineWidth=0);
        drawMountainsSVG("canvas5",mtps5,mtLineColor=mtFillColorList[5],mtFillColor=mtFillColorList[5],mtLineWidth=0);
        drawMountainsSVG("canvas6",mtps6,mtLineColor=mtFillColorList[6],mtFillColor=mtFillColorList[6],mtLineWidth=0);
        drawMountainsSVG("canvas7",mtps7,mtLineColor=mtFillColorList[7],mtFillColor=mtFillColorList[7],mtLineWidth=0);

        /*
        //canvas模式绘制山脉
        let mtFillColorList=['#ff3333','#ff4444','#ff5555','#ff6666','#ff7777','#ff8888','#ff9999','#ffaaaa','#ffbbbb','#ffcccc'];
        drawMountains("canvas0",mtps0,mtLineColor=mtFillColorList[0],mtFillColor=mtFillColorList[0],mtLineWidth=0);
        drawMountains("canvas1",mtps1,mtLineColor=mtFillColorList[1],mtFillColor=mtFillColorList[1],mtLineWidth=0);
        drawMountains("canvas2",mtps2,mtLineColor=mtFillColorList[2],mtFillColor=mtFillColorList[2],mtLineWidth=0);
        drawMountains("canvas3",mtps3,mtLineColor=mtFillColorList[3],mtFillColor=mtFillColorList[3],mtLineWidth=0);
        drawMountains("canvas4",mtps4,mtLineColor=mtFillColorList[4],mtFillColor=mtFillColorList[4],mtLineWidth=0);
        drawMountains("canvas5",mtps5,mtLineColor=mtFillColorList[5],mtFillColor=mtFillColorList[5],mtLineWidth=0);
        drawMountains("canvas6",mtps6,mtLineColor=mtFillColorList[6],mtFillColor=mtFillColorList[6],mtLineWidth=0);
        drawMountains("canvas7",mtps7,mtLineColor=mtFillColorList[7],mtFillColor=mtFillColorList[7],mtLineWidth=0);
        */

        //----------------------------
        // 调用动画函数

        moveMt("canvas0",speed=1);
        moveMt("canvas1",speed=2);
        moveMt("canvas2",speed=3);
        moveMt("canvas3",speed=4);
        moveMt("canvas4",speed=5);
        moveMt("canvas5",speed=6);
        moveMt("canvas6",speed=7);
        moveMt("canvas7",speed=8);
    </script>
</html>

精简圆滑曲线版:

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <title>千山万水</title>
        <style>
            body {
                margin: 0;
                padding: 0;
            }
            #container {
                margin: 0;
                padding: 0;
                position:absolute;
                top:0;
                left:0;
                width:100%;
                height:100vh;
                overflow:hidden;
                background-color: #fff;
                box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
                background-color:#ff3333;
            }
            canvas {                
                margin: 0;
                padding: 0;
                position:absolute;
                top:0;
                left:-100px;
            }
            svg{
                margin: 0;
                padding: 0;
                position:absolute;
                top:0;
                left:0;
            }
        </style>
    </head>
    <body>
        <!-- 千山万水 -->
        
        <div id="container"></div>

        
    </body>
    <script>
        // 使用SVG绘制山脉

        // 画布高度和页面高度相同
        let canvasHeight=window.innerHeight;
        console.log(`canvasHeight=${canvasHeight}`);

        //----------------------------       

        function generateMountainsCurvePoints (pointCount=9,mtWidthBase=30,mtWidthVariation=120,mtHeightBase=100,mtHeightVariation=200) {
            // 生成随机的山脉曲线点
            // 山脉曲线点数组
            let mountainCurvePoints=[];
            
            // 起点
            let currentX=0;
            let currentY=canvasHeight;

            // 随机生成山脉曲线点
            for (let i = 0; i < pointCount; i++) {
                mountainCurvePoints.push([currentX,currentY]);
                //生成随机偏移量
                //x偏移量限定区间100-200
                let xOffset=Math.random()*mtWidthVariation+mtWidthBase;
                currentX+=xOffset;
                //y值随机变幻区间
                currentY=Math.random()*mtHeightVariation+mtHeightBase;
                currentY=canvasHeight-currentY;
            }             
            // 终点
            mountainCurvePoints.push([currentX+100,canvasHeight]);
            // 返回结果数组
            return mountainCurvePoints;
        }
        //----------------------------
        
        //----------------------------
        function drawMountainsSVG (canvasId,mountainCurvePoints,mtLineColor='rgb(255,0,0,1)',mtFillColor='rgb(255,0,0,1)',mtLineWidth=1) {
            // 绘制山脉
            //创建SVG元素svg
            const svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
            // 设置SVG元素的属性
            svg.setAttribute('width',mountainCurvePoints[mountainCurvePoints.length-1][0]);
            svg.setAttribute('height',canvasHeight);
            svg.setAttribute('id',canvasId);
            // 设置SVG元素的属性
            svg.setAttribute('viewBox',`0 0 ${mountainCurvePoints[mountainCurvePoints.length-1][0]} ${canvasHeight}`);
            // 创建path元素
            const path = document.createElementNS('http://www.w3.org/2000/svg','path');
            // 构造path文本字符串
            let pathText=`M ${mountainCurvePoints[0][0]} ${mountainCurvePoints[0][1]} `;
            // 按照mountainCurvePoints的坐标点,绘制山脉曲线
            for (let i = 0; i < mountainCurvePoints.length-1; i++) {
                //构造path文本字符串
                // pathText+=`L ${mountainCurvePoints[i][0]} ${mountainCurvePoints[i][1]} `;
                // 绘制圆滑曲线,使用三次贝塞尔曲线
                const p0 = mountainCurvePoints[i];
                const p1 = mountainCurvePoints[i + 1];
                const cp1 = { x: (p0[0] + p1[0]) / 2, y: p0[1] }; // 控制点1,通常在两个点之间垂直于线段的中点
                const cp2 = { x: (p0[0] + p1[0]) / 2, y: p1[1] }; // 控制点2,通常在两个点之间垂直于线段的中点,但稍微偏移以避免直线连接
                if (i === 0) {
                    pathText += `M ${p0[0]} ${p0[1]}`; // Move to the first point
                } else {
                    pathText += `C ${cp1.x} ${cp1.y}, ${cp2.x} ${cp2.y}, ${p1[0]} ${p1[1]} `; // Cubic Bezier curve to the next point
                }


            }
            // 闭合曲线
            pathText+='Z';
            // 设置path元素的属性
            path.setAttribute('d',pathText);
            // 绘制山脉曲线,使用随机颜色
            path.setAttribute('stroke',`${mtLineColor}`);
            path.setAttribute('stroke-width',`${mtLineWidth}`);
            path.setAttribute('stroke-linecap','round');
            path.setAttribute('stroke-linejoin','round');
            //添加linearGradient线性渐变填充
            const linearGradient  = document.createElementNS('http://www.w3.org/2000/svg','linearGradient');
            linearGradient.setAttribute('id',`linearGradientFill${canvasId}`);
            linearGradient.setAttribute('x1','0%');
            linearGradient.setAttribute('y1','0%');
            linearGradient.setAttribute('x2','0%');
            linearGradient.setAttribute('y2','100%');
            const stop1  = document.createElementNS('http://www.w3.org/2000/svg','stop');
            stop1.setAttribute('stop-color',`${mtFillColor}`);
            stop1.setAttribute('offset','0%'); 
            linearGradient.appendChild(stop1);
            const stop2  = document.createElementNS('http://www.w3.org/2000/svg','stop');
            stop2.setAttribute('stop-color',`orange`);
            stop2.setAttribute('offset','100%'); 
            linearGradient.appendChild(stop2);
            // 添加linearGradient元素到svg元素
            svg.appendChild(linearGradient);

            // 填充颜色
            // path.setAttribute('fill',`${mtFillColor}`);
            path.setAttribute('fill',`url(#linearGradientFill${canvasId})`);            
            
            // 添加path元素到svg元素
            svg.appendChild(path);
            // 添加svg元素到container元素
            document.getElementById('container').appendChild(svg);

            
        }
        //----------------------------
        // 动画:canvas水平平移动画循环
        function moveMt(canvasId,speed=10,stopGate=1000000) {
            // 获取Canvas元素
            let mt = document.getElementById(canvasId);
            // 通过修改left属性实现水平平移动,left的属性无法直接获取,需要通过getComputedStyle()方法获取当前left值,,
            // 所能直接修改left属性,实现水平平移动
            let mtLeft=parseInt(getComputedStyle(mt).left);
            // console.log("canvas left:"+mtLeft);
            mt.style.left=mtLeft-speed+"px";
            //动画循环
            let animationId=requestAnimationFrame(()=>{
                    moveMt(canvasId,speed);
                });
            // 触发结束循环的条件是left绝对值大于等于当前画布的宽度

            // console.log(`stopGate=${stopGate}`);
            if(mtLeft<=-stopGate){
                console.log(`${canvasId} 结束循环`);           
                cancelAnimationFrame(animationId);  
            }

        }
        //----------------------------
        // 绘制山脉
        let pointCount=10000;
        let mtWidthVariation=50;
        let mtHeightVariation=145;
        mtps0=generateMountainsCurvePoints(pointCount=pointCount,mtWidthBase=30,mtWidthVariation=mtWidthVariation,mtHeightBase=870,mtHeightVariation=mtHeightVariation);
        mtps1=generateMountainsCurvePoints(pointCount=pointCount,mtWidthBase=30,mtWidthVariation=mtWidthVariation,mtHeightBase=800,mtHeightVariation=mtHeightVariation);
        mtps2=generateMountainsCurvePoints(pointCount=pointCount,mtWidthBase=30,mtWidthVariation=mtWidthVariation,mtHeightBase=720,mtHeightVariation=mtHeightVariation);
        mtps3=generateMountainsCurvePoints(pointCount=pointCount,mtWidthBase=30,mtWidthVariation=mtWidthVariation,mtHeightBase=630,mtHeightVariation=mtHeightVariation);
        mtps4=generateMountainsCurvePoints(pointCount=pointCount,mtWidthBase=30,mtWidthVariation=mtWidthVariation,mtHeightBase=530,mtHeightVariation=mtHeightVariation);
        mtps5=generateMountainsCurvePoints(pointCount=pointCount,mtWidthBase=30,mtWidthVariation=mtWidthVariation,mtHeightBase=420,mtHeightVariation=mtHeightVariation);
        mtps6=generateMountainsCurvePoints(pointCount=pointCount,mtWidthBase=30,mtWidthVariation=mtWidthVariation,mtHeightBase=320,mtHeightVariation=mtHeightVariation);
        mtps7=generateMountainsCurvePoints(pointCount=pointCount,mtWidthBase=30,mtWidthVariation=mtWidthVariation,mtHeightBase=200,mtHeightVariation=mtHeightVariation);

        //----------------------------
        //SVG模式绘制山脉
        let mtFillColorList=['#ff4444','#ff5555','#ff6666','#ff7777','#ff8888','#ff9999','#ffaaaa','#ffbbbb','#ffcccc','#ffdddd','#ffeeee','#ffffff'];
        drawMountainsSVG("canvas0",mtps0,mtLineColor=mtFillColorList[0],mtFillColor=mtFillColorList[0],mtLineWidth=0);
        drawMountainsSVG("canvas1",mtps1,mtLineColor=mtFillColorList[1],mtFillColor=mtFillColorList[1],mtLineWidth=0);
        drawMountainsSVG("canvas2",mtps2,mtLineColor=mtFillColorList[2],mtFillColor=mtFillColorList[2],mtLineWidth=0);
        drawMountainsSVG("canvas3",mtps3,mtLineColor=mtFillColorList[3],mtFillColor=mtFillColorList[3],mtLineWidth=0);
        drawMountainsSVG("canvas4",mtps4,mtLineColor=mtFillColorList[4],mtFillColor=mtFillColorList[4],mtLineWidth=0);
        drawMountainsSVG("canvas5",mtps5,mtLineColor=mtFillColorList[5],mtFillColor=mtFillColorList[5],mtLineWidth=0);
        drawMountainsSVG("canvas6",mtps6,mtLineColor=mtFillColorList[6],mtFillColor=mtFillColorList[6],mtLineWidth=0);
        drawMountainsSVG("canvas7",mtps7,mtLineColor=mtFillColorList[7],mtFillColor=mtFillColorList[7],mtLineWidth=0);



        //----------------------------
        // 调用动画函数

        moveMt("canvas0",speed=1);
        moveMt("canvas1",speed=2);
        moveMt("canvas2",speed=3);
        moveMt("canvas3",speed=4);
        moveMt("canvas4",speed=5);
        moveMt("canvas5",speed=6);
        moveMt("canvas6",speed=7);
        moveMt("canvas7",speed=8);
    </script>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值