今天和大家分享有關(guān)于“怎么實(shí)現(xiàn)實(shí)現(xiàn)光線沿不規(guī)則路徑運(yùn)動(dòng)?”這個(gè)問題的相關(guān)解決方法,在之前小編分享過html5canvas畫布中就有不少的小伙伴們就問到了今天的這個(gè)問題,那么經(jīng)過一番的搜索我們我們就來看看怎么實(shí)現(xiàn)吧!
svg讓動(dòng)畫沿著不規(guī)則路徑運(yùn)動(dòng)
查閱svg文檔后發(fā)現(xiàn),svg動(dòng)畫運(yùn)動(dòng)有兩種實(shí)現(xiàn)方式,且都非常簡(jiǎn)單,但對(duì)于100%實(shí)現(xiàn)設(shè)計(jì)師給出的效果有很大的距離
使用offset-path偏移路徑和offset-roate偏移角度讓元素沿著不規(guī)則路徑走:
<!DOCTYPE html>
<html>
<head>
<title>offset-path/offset-roate</title>
</head>
<style type="text/css">
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
background: #000;
}
.line {
width: 80px;
height: 3px;
position: absolute;
background: red;
offset-path: path("M10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500");
animation: move 10s linear infinite;
}
@keyframes move {
100% {
offset-distance: 2000px;
}
}
.line1 {
position: absolute;
left: 100px;
width: 20px;
height: 20px;
border-radius: 50%;
background: red;
offset-path: path("M0,0a72.5,72.5 0 1,0 145,0a72.5,72.5 0 1,0 -145,0");
offset-rotate: 0deg;
animation: load 1.8s cubic-bezier(0.86, 0, 0.07, 1) infinite;
animation-delay: 0.147s;
animation-fill-mode: forwards;
}
@keyframes load {
from {
offset-distance: 0;
}
to {
offset-distance: 100%;
}
}
</style>
<body>
<h2>路徑偏移</h2>
<div class="line"></div>
<svg width="100%" height="600px" version="1.0" id="svg1">
<path d="M10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500" fill="#tranparent" stroke="#FFF"></path>
</svg>
<h2>角度偏移</h2>
<div class="line1">
</div>
</body>
</html>
此種方式的限制是滾動(dòng)元素?zé)o法隨路徑進(jìn)行沒有規(guī)律的變化
使用stroke-dasharray和stroke-dashoffset讓路徑邊線動(dòng)起來
stroke-dasharray:設(shè)置shap和text 邊框虛線的實(shí)線長(zhǎng)度與實(shí)線之間的間隔(虛線長(zhǎng)度)
stroke-dashoffser:設(shè)置邊框線條相對(duì)于默認(rèn)位置的偏移(正值:向左,負(fù)值:向右):
<!DOCTYPE html>
<html>
<head>
<title>stroke-dasharray/stroke-dashoffser</title>
</head>
<style type="text/css">
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
background: #000;
color: #fff;
}
.move {
animation: moving 5s infinite;
}
@keyframes moving {
0% {
stroke-dashoffset: 80px;
}
100% {
stroke-dashoffset: -1600px;
}
}
</style>
<body>
<h2>設(shè)置stroke-dasharray</h2>
<b>storke-dasharray設(shè)置為80 ,此時(shí)實(shí)線和實(shí)線間隔一樣</b>
<svg width="100%" height="600px" version="1.0" id="svg1">
<path d="M10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500" fill="#tranparent" stroke="#FFF" stroke-dasharray="80"></path>
</svg>
<b>storke-dasharray設(shè)置為80 320,此時(shí)實(shí)線和是實(shí)線間隔的1/4</b>
<svg width="100%" height="600px" version="1.0" id="svg1">
<path d="M10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500" fill="#tranparent" stroke="#FFF" stroke-dasharray="80 320"></path>
</svg>
<h2>設(shè)置stroke-dashoffset讓邊線相對(duì)于初始位置發(fā)生偏移</h2>
<svg width="100%" height="600px" version="1.0" id="svg1">
<path d="M10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500" fill="#tranparent" stroke="#FFF" stroke-dasharray="80 320" stroke-dashoffset="40"></path>
</svg>
<h2>通過設(shè)置stroke-dasharray 和 stroke-dashoffset讓邊線動(dòng)起來</h2>
<svg width="100%" height="600px" version="1.0" id="svg1">
<path d="M10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500" fill="#tranparent" stroke="#FFF"></path>
<path d="M10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500" fill="#tranparent" stroke="red" stroke-dasharray="80 1600" stroke-dashoffset="0" class="move"></path>
</svg>
</body>
</html>
此種方式通過邊框偏移的效果可以設(shè)置跟隨路徑的滾線條,但是無法設(shè)置線條的光線效果,即實(shí)線的陰影和實(shí)線的漸變效果(漸變區(qū)域需隨著偏移路徑的變化而變化)
canvas實(shí)現(xiàn)線條延不規(guī)則路徑運(yùn)動(dòng)
線條實(shí)現(xiàn)
對(duì)于不規(guī)則路徑,如果直接用畫線條的方式實(shí)現(xiàn)光線,需要計(jì)算每一個(gè)開始點(diǎn)和結(jié)束點(diǎn)的位置,中間還可能存在轉(zhuǎn)折點(diǎn),計(jì)算起來非常麻煩,不可取
故這邊采取canvas組合圖形的模式,取線條和一個(gè)圖形重疊部分(類似于燈罩)來實(shí)現(xiàn)光線效果
組合前
組合后
<!DOCTYPE html>
<html>
<head>
<title>canvas實(shí)現(xiàn)不規(guī)則路徑光效</title>
</head>
<style type="text/css">
body {
background: #000;
}
#wrap {
position: absolute;
width: 1200px;
height: 600px
}
</style>
<body>
<div id="wrap">
<canvas id="canvas" width="1200" height="600"></canvas>
</div>
</body>
<script type="text/javascript">
var path = 'M 10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500';
var list = path.match(/([A-Z]([^A-Z]){1,})/g).map(item => {
return {
x: item.split(' ')[1],
y: item.split(' ')[2],
action: item.split(' ')[0],
}
});//獲取每個(gè)點(diǎn)位置
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.strokeStyle = 'rgba(255,255,255,1)';
function drawPath() {
ctx.lineWidth = 3;
ctx.beginPath();
list.forEach(item => {
if(item.action == 'M') ctx.moveTo(item.x, item.y);
if(item.action == 'L') ctx.lineTo(item.x, item.y);
});
ctx.stroke();
}
drawPath();
function drawLine() {
//設(shè)置圖形組合方式 默認(rèn)source-over
ctx.globalCompositeOperation = "destination-in";
ctx.lineWidth = 60;
ctx.beginPath();
ctx.moveTo(40, 80);
ctx.lineTo(200, 80);
ctx.stroke();
}
drawLine();
</script>
</html>
讓線條動(dòng)起來
當(dāng)我們實(shí)現(xiàn)好線條剩下就需要讓線條動(dòng)起來,由于線條是通過燈罩的方式來實(shí)現(xiàn)的,讓線條運(yùn)動(dòng)只需要讓燈罩動(dòng)起來就好
<!DOCTYPE html>
<html>
<head>
<title>canvas實(shí)現(xiàn)不規(guī)則路徑光效</title>
</head>
<style type="text/css">
body {
background: #000;
}
#wrap {
position: absolute;
width: 1200px;
height: 600px
}
</style>
<body>
<div id="wrap">
<canvas id="canvas" width="1200" height="600"></canvas>
</div>
</body>
<script type="text/javascript">
var path = 'M 10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500';
var list = path.match(/([A-Z]([^A-Z]){1,})/g).map(item => {
return {
x: item.split(' ')[1],
y: item.split(' ')[2],
action: item.split(' ')[0],
}
});//獲取每個(gè)點(diǎn)位置
var step = 3;
var x1, x2, y1, y2;//確定路徑中最大最小點(diǎn)
var timer;
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.strokeStyle = 'rgba(255,255,255,1)';
ctx.shadowColor = 'rgba(255,255,255,1)';
ctx.lineCap = 'round';
ctx.shadowBlur = 3;
list.forEach(item => {
x1 = !x1 || Number(item.x) < x1 ? Number(item.x) : x1;
y1 = !y1 || Number(item.y) < y1 ? Number(item.y) : y1;
x2 = !x2 || Number(item.x) > x2 ? Number(item.x) : x2;
y2 = !y2 || Number(item.y) > y2 ? Number(item.y) : y2;
});
function drawPath() {
ctx.lineWidth = 3;
ctx.beginPath();
list.forEach(item => {
if(item.action == 'M') ctx.moveTo(item.x, item.y);
if(item.action == 'L') ctx.lineTo(item.x, item.y);
});
//添加光效漸變
var grd = ctx.createLinearGradient(arrLine[arrLine.length - 1].x, arrLine[arrLine.length - 1].y, arrLine[0].x, arrLine[0].y);
grd.addColorStop(0, 'rgba(255, 255, 255, 0)'); //定義漸變線起點(diǎn)顏色
grd.addColorStop(1, 'rgba(255, 255, 255, 1)'); //定義漸變線結(jié)束點(diǎn)的顏色
ctx.strokeStyle = grd;
ctx.stroke();
}
//設(shè)計(jì)合適的初始線條狀態(tài)
var arrLine = Array(10).fill(0).map((item, inx) => {
return {
x: x1 - 20 * inx,
y: y1 + 30,
}
});
//隨時(shí)間變化圖形路徑
function getArrLine() {
var isEnd
arrLine = arrLine.map(item => {
var x = item.x;
var y = item.y;
if(x < x2 - 30) {
x = x + step > x2 -30 ? x2 - 30 : x + step;
} else if(x == x2 -30 && y < y2) {
y = y + step > y2 ? y2 : y + step;
} else {
isEnd = true;
}
return {
x,
y
}
});
isEnd && timer && cancelAnimationFrame(timer);
}
//繪制圖形
function drawLine() {
//設(shè)置圖形組合方式 默認(rèn)source-over
ctx.globalCompositeOperation = "destination-in";
ctx.lineWidth = 70;
ctx.beginPath();
arrLine.forEach((item, inx) => {
if(inx == 0) {
ctx.moveTo(item.x, item.y);
} else {
ctx.lineTo(item.x, item.y);
}
})
ctx.stroke();
}
function start() {
ctx.clearRect(0, 0, 1200, 600);
ctx.globalCompositeOperation = 'source-over';
drawPath();
drawLine();
getArrLine();
timer = requestAnimationFrame(start);
}
timer = requestAnimationFrame(start);
</script>
</html>
這種實(shí)現(xiàn)方式也有一定的條件限制,那就是路徑可大體抽象成為一個(gè)有一定規(guī)律的圖型或者線條,比如上面demo中路徑可抽象成為一個(gè)矩形的兩邊,或者是2條連接的直線
我們必須從沒有具體規(guī)則的路徑中抽象出一個(gè)大體的規(guī)則,不同路徑規(guī)則不同
上面的例子就是將不規(guī)則路徑抽象成了一個(gè)直角的規(guī)則路徑
可優(yōu)化點(diǎn)
這邊找到了2個(gè)可優(yōu)化的點(diǎn)
1.時(shí)間方向上: 為了讓動(dòng)畫消耗較小,代碼中的定時(shí)器已經(jīng)用的是requestAnimationFrame, 但是由于光線的特殊性(自帶模糊效果),為了性能更加,嘗試了2次requestAnimationFrame調(diào)用一次繪圖的方式,效果較前者未有明顯區(qū)別
2.繪圖方向上: 從上圖可發(fā)現(xiàn),燈罩每次只圈出路徑的一部分,故繪圖中不需要每次都繪制全部路徑,只需要找出燈罩前后的路徑點(diǎn),將這一段路徑繪制出來就好
坑點(diǎn)
在完成這個(gè)動(dòng)動(dòng)畫效果之后遇到一個(gè)至今原因不明的bug,隨著屏幕放置時(shí)間的變長(zhǎng),動(dòng)畫越來越慢,打開任務(wù)管理器,未見內(nèi)存泄漏或者cpu使用率過高。打開performance,發(fā)現(xiàn)頁面調(diào)幀嚴(yán)重,屏幕幀數(shù)越來越低,單個(gè)Frame CPU time越來越長(zhǎng),范圍來看,script和render和paint耗時(shí)未發(fā)生線性變化,只有system時(shí)間越來越來長(zhǎng),越來越長(zhǎng),期望能被大佬告知原因
一開始
到后來
解決的辦法較為...,光線每循環(huán)一個(gè)周期,我銷毀了之前的canvas并新建了canvas,上層規(guī)避了system time不知道為什么越來越長(zhǎng)的問題
chrome版本:80.0.3987.163(正式版本) (64 位)
那么到此這篇關(guān)于“怎么實(shí)現(xiàn)實(shí)現(xiàn)光線沿不規(guī)則路徑運(yùn)動(dòng)?”這個(gè)問題的詳細(xì)解決方法和做法我們就分享到這里啦!更多有關(guān)于html5 canvas 這方面的相關(guān)內(nèi)容我們都能在W3Cschool搜索了解更多的相關(guān)內(nèi)容!