果圖
在渲染頁面的過程中,進度條是我們最為常見的,有條形進度條,圓環進度條,圓形進度條。今天,我們就來實現一下圓形進度條。
用到的知識點:
首先,我們創建一個box,作為父級容器,定寬定高(160px),定位(position:relative);
.box{
width: 160px;
height: 160px;
margin: 200px auto;
position: relative;
}
圓形進度條,分為左右兩部分,利用時間差,通過動畫關鍵幀來達到進度效果。
接下來,我們首先實現左側部分,創建一個div,類名為left-box,定寬定高(80px,160px);定位(position:absolute);
在left-box下創建一個div為子元素,類名為left-tran 和 left。接下來,設置left-tran樣式。
我們要明白,進度條的實現是通過時間差,改變邊框的顏色。通俗一點:就是一個div,width和height都為0px;邊框寬度設為100%;然后top和left為一組,bottom和right為一組,分別設置不同的顏色。效果如下圖:
接下來。將父級設置overflow:hidden。超出部分隱藏。這樣就只顯示一半的邊框,隨后便利用transform:rotate旋轉45deg,邊框邊圓角得到最終效果,如下圖:
最后設置關鍵幀,不同的邊框顏色就會相互轉變
這樣。左側的樣式就已經完成,右側的與之相似,這里,我們就不在這里多加描述。
待完成右側樣式,兩個關鍵幀動畫相互協調,就可以完成一個圓形進度條啦。
今天的全部代碼如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> * { padding: 0px; margin: 0px; } .box { width: 200px; height: 200px; position: relative; margin: 100px auto; box-sizing: border-box; } .left-box{ width: 100px; height: 200px; position: absolute; left: 0px; top: 0px; box-sizing: border-box; overflow: hidden; } .left-tran{ width: 0px; height: 0px; border: 100px solid; box-sizing: border-box; transform: rotate(45deg); border-radius: 50%; } .left{ border-left: 100px solid #e3e4e5; border-bottom: 100px solid #e3e4e5; animation: leftmove 10s linear infinite; } @keyframes leftmove{ 0%{ transform: rotate(45deg); } 50%{ transform: rotate(45deg); } 100%{ transform: rotate(225deg); } } .right-box{ width: 100px; height: 200px; position: absolute; top: 0px; right: 0px; box-sizing: border-box; overflow: hidden; } .right-tran{ width: 0px; height: 0px; border: 100px solid; position: absolute; top: 0px; right: 0px; transform: rotate(45deg); border-radius: 50%; } .right{ border-right: 100px solid #e3e4e5; border-top: 100px solid #e3e4e5; animation: rightmove 10s linear infinite; } @keyframes rightmove{ 0%{ transform: rotate(45deg); } 50%{ transform: rotate(225deg); } 100%{ transform: rotate(225deg); } } </style> </head> <body> <div class="box"> <div class="left-box"> <div class="left-tran left"></div> </div> <div class="right-box"> <div class="right-tran right"></div> </div> </div> </body> </html>
進度條,結合和時間戳,就可以很好的完成一個完美的時間進度條了。
希望今天的知識點對大家有所幫助。
端大佬們,你們會怎么去實現呢?
用css畫出一個圓圈,里面有個叉號(不是字母x),你會怎么實現?
哈嘍家人們,這是我今天去面試的一道筆試題。看到這個題目我第一時間想到的是用偽元素來實現,可握起筆我的手和腦子好像失聯了,不知道如何下筆,寫不出來!完蛋了,涼了!在這大環境下好不容易得來的面試機會,只能怪自己沒好好準備。
沒關系,還有面試!還有希望,先安慰一下自己,說不定背的八股文在面試的時候派上用場了!主打就是一個心態樂觀,面試重在參與!面試結束了,run回家了,死心了,真就是重在參與了。問的一個沒背,答得亂七八糟!
拜拜了家人們,今天也是陪跑的一天。
anvas簡介
在學習一項新技術之前,先了解這項技術的歷史發展及成因會幫助我們更深刻的理解這項技術。
歷史上,canvas最早是由Apple Inc. 提出的,在Mac OS X webkit中創建控制板組件使用,而在canvas稱為HTML草案及標準之前,我們是通過一些替代方式去繪圖的,比如為人所詬病的Flash,以及非常強大的SVG(Scalable Vector Graphics,可伸縮的矢量標記圖),還有只能在IE(IE 5.0以上的版本)中使用的VML(Vector Markup Language,矢量可標記圖)。甚至于有些前端可以使用div+css來完成繪圖。
總的來說,沒有canvas的時候,在瀏覽器繪制圖形是比較復雜的,而在canvas出現之后,繪制2D圖形相對變得容易了。
NOTE: 用div繪制一些簡單的圖形,如矩形,圓形,三角形,梯形,倒也算是沒那么復雜。
但canvas也有缺點。因為canvas本質上是一個與 分辨率相關 的 位圖畫布 ,也就注定了在不同分辨率下,canvas繪制的內容顯示的時候會有所不同。此外,canvas繪制的內容 不屬于任何DOM元素 ,在瀏覽器的元素查看器中也找不到,那自然無法檢測鼠標點擊了canvas中的哪個內容,很顯然,這兩方面,canvas都是不如SVG的。
舉個例子:如果使用CSS設置canvas元素的尺寸,那可能會導致繪制出來的圖形變得扭曲,如長方形變正方形,圓形變橢圓等,這是因為畫布尺寸和元素尺寸是不一樣的,畫布會自動適應元素的尺寸,如果二者是成比例的,那么畫布就會等比例縮放,不會出現扭曲。
這么說來,canvas有這么明顯的缺點,那直接使用SVG豈不是更好?
No,聽過一句話嗎?沒有完美的方案,只有適不適合。
SVG是基于XML的,那么就說明,SVG里面的元素都可以認為是 DOM元素 ,可以啟用DOM操作,同時,SVG中每個繪制的圖像均被視為對象,若SVG對象屬性變化,瀏覽器會自動重現圖形。
以上是SVG的優勢,但通過這個優勢,我們也能發現一些問題:
回到主題。
canvas是通過JavaScript進行2D圖形的繪制,而 <canvas> 標簽本身是沒有任何繪制能力的,它僅僅是一個容器。在繪制時,canvas是逐像素的進行渲染的,一旦圖形繪制完成,該元素就不再被瀏覽器所關注(腳本執行結束,繪制的圖形也不屬于DOM)。
值得注意的是,在HTML標準( whatwg標準 )中明確的指出: Authors should not use the canvas element in a document when a more suitable element is available. 所以,不要濫用元素。
canvas目前幾乎被所有的瀏覽器支持,但是IE 9.0 之前的版本不支持 canvas 元素
canvas是一個HTML元素,所以要使用canvas,首先需要:
<canvas id="canvas" width="600" height="300">
當前瀏覽器不支持canvas
</canvas>
在第一行HTML代碼中可以看到兩個屬性: width 和 height ,它指明了畫布的寬高,在上文中提到過,不要使用CSS規定尺寸,因為當CSS規定的尺寸和畫布尺寸比例不一致時,無法成比例縮放,導致繪制出來的圖形變得扭曲。在沒有設置畫布大小時,canvas默認會初始化成300px * 150px的畫布。
“當前瀏覽器不支持canvas”是元素的內容,但他只是作為一個后備內容(即 fallback content ),只有當瀏覽器不支持canvas時,這個內容才會被顯示出來。
canvas元素本身沒有繪制能力,只是作為一個容器,所以需要通過JavaScript這類腳本進行繪制:
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
上面的HTML+JS代碼是使用canvas所必須的,無論要繪制什么內容,這幾行代碼不可缺少。
getContext() 是canvas元素提供的方法,用于獲取繪制上下文(或者說渲染上下文,The rendering context),他只有一個參數:上下文格式。這里傳入 2d 表示獲取2D圖像繪制環境。由于 getContext 是canvas元素提供的方法,故我們可以通過檢測 getContext 方法的存在性來檢查瀏覽器的支持性。
context變量的類型是 CanvasRenderingContext2D 。
渲染上下文不好理解,可以理解為畫圖用的筆刷。
在畫布中如何確定繪制的位置?是坐標。
在canvas中,畫布的左上角為原點,橫軸為x軸表示寬,縱軸為y軸表示高[^1]。原點的位置是可以移動的,我們暫時不考慮原點的移動問題。
在 w3c school 中,將canvas提供的繪制API大致分為以下幾種[^2]:
在上面這個例子中,包含了矩形,圓形,線,文字及“文字”幾大塊內容,細講下去,會涉及到不少API,會使得本文變得很長,而且沒有必要,值得一提的是貝塞爾曲線,這是二維圖形應用程序的數學曲線,一般的矢量圖形軟件就是通過它來精確畫出曲線的,貝塞爾曲線是計算機圖形學中相當重要的參數曲線[^3]。
以上圖片按順序分別是一次貝塞爾曲線,二次貝塞爾曲線,三次貝塞爾曲線。從圖中,可以很清楚的看到,一次貝塞爾曲線實際上是一條直線。當然,還有更高階次的曲線,不過canvas只提供了二次和三次貝塞爾曲線。
以二次貝塞爾曲線的API為例:
quadraticCurveTo(cp1x, cp1y, x, y);
(cp1x, cp1y)表示控制點坐標,(x, y)表示結束點坐標。這里還缺少一個起始點坐標,假設是(x0, y0),那這個(x0, y0)是誰?
就是在調用 quadraticCurveTo 函數時,context(繪制上下文)所處的坐標。舉個例子:
var cxt = canvas.getContext('2d'); // 認為canvas已經獲取到
cxt.beginPath();
cxt.moveTo(120, 90);
cxt.quadraticCurveTo(130, 80, 130, 70);
cxt.quadraticCurveTo(115, 70, 115, 50);
cxt.quadraticCurveTo(115, 30, 155, 30);
cxt.quadraticCurveTo(195, 30, 195, 50);
cxt.quadraticCurveTo(195, 70, 155, 70);
cxt.quadraticCurveTo(135, 90, 120, 90);
cxt.stroke();
這段代碼運行結果就是一個對話框(在第一張圖片中體現),可以看到,在調用二次貝塞爾曲線之前,我們設置了起點,即,將筆刷移動到坐標(120, 90),在之后調用中,都是以前一次貝塞爾曲線的終點作為本次曲線的起點。
這時候可能會有人問:我去掉這個 moveTo 的調用是不是就畫不出來了?如果后續是調用 lineTo 函數,那還真就畫不出來了。但是別忘了,還有一次貝塞爾曲線,這就是條直線,他是以(cp1x, cp1y)為起點,(x,y)為終點的一條直線。所以說,去掉 moveTo 后,只會影響到第一條曲線的繪制。但是如果刪除最后一行代碼 stroke() ,那么程序運行結束時,在瀏覽器上啥都看不到。
由此,我們應該思考另一個問題:為什么 stroke() 函數是必須的呢?
其實,canvas是一種基于狀態的繪制,依照此,可以將canvas提供的API分為兩種:狀態設置,具體繪制。
stroke() 、 fill() 等函數就是將內容繪制到canvas畫布容器中的函數。
arc() 、 lineTo() 、 rect() 等函數就是設置筆刷狀態的函數。
在那種玄幻類型的電影、電視劇里面就經常能看到某個道士虛空畫符,畫完之后往前一推,就印在了對應的符或者人身上了。
道士虛空畫符,這個過程就像是canvas設置筆刷狀態的過程。
往前一推,這個就是具體的繪制了,怎么繪制咱不知道,反正這符是畫上去了。(前文提到過,canvas是 逐像素渲染 的)
“文字”的繪制,注意,這個文字是打了引號的,普通文字,我們繪制只需要調用 fillText() 即可,而這里所指的文字是 點陣字體 ,在單片機或者LCD這類程序中,通過點亮一系列的點,顯示出文字或圖案,點亮的過程較為復雜,可以簡單的理解為LCD上的像素點置為1時點亮該點,為0時不點亮(實際可能相反)。那么canvas這里的“文字”繪制也是一樣的道理,通過建立文字對應的字體庫,當需要繪制某個文字的時候,在字體庫中找到對應的文字點陣,然后將點陣中標志為1的位置點亮(填充)即可。
實際操作時,可能并不是點亮這么簡單,你可能會想要制作出更酷的內容,用圓形去填充,用矩形去填充,甚至說想要制作出動態爆炸的效果,這時候就牽扯到一些其他的計算了。
上圖是一個用矩形填充的示例,數字對應8x8的點陣。
先思考一個問題,假設現在我們已經學會了繪制一個圓形的方法,現在要求做出一個和物理學相關的動畫:平拋運動。
現在該如何去實現呢?
可能看到這個問題的時候,有些人瞬間懵圈了:我就學了個繪制圓的函數,你就讓我模擬這么高難度的動畫,你這分明是想謀害鄭!
可能也有人會想到,平拋運動,在高中物理學中學到過,基本都只是研究一個小球的問題,在2維平面中,這小球完全可以視作一個圓,可不就只需要學會畫圓就行了?
經此,我們繼續往下思考,在平拋運動中的小球,假設水平方向設有初始速度v0,除了重力外,不受到其他外力影響,也即存在一個重力加速度g(為了計算簡單,我們可以簡單的設為 g = 10m/s^2 ),同時豎直方向沒有初速度vh(或稱 vh = 0; ),如下圖:
從圖中,我們可以看到一些很有意思的現象,如:小球的水平方向剛好和canvas畫布的橫軸一致,豎直方向也和縱軸方向保持一致。
然后由平拋運動對應的物理公式:
// 豎直方向無初速度,水平方向沒有外力
x = v0 * t; // 水平方向位移
h = 1/2 * g * t * t; // 豎直方向位移
// 豎直方向有初速度
h = vh * t - 1/2 * g * t * t; // 豎直方向位移
發現(x, h)和canvas上的坐標(x, y)是一致的,而且我們也不是在做物理題,也就是說,v0, t, g, vh這些參數都是已知的,我們唯一需要做的就是,計算出任意時刻的(x, h),也即小球在canvas上的坐標(x, y)。
分析結束,我們現在可以得到小球在任意時刻的位置坐標,那么我們也就可以在畫布上畫出來任意時刻的小球。
針對上面的分析,可能會有人說:你這不對,你這個應該是具有特殊性的吧,小球未必是從左邊拋出去的,從右邊也可以啊,向上拋也可以。
的確,上面的分析只是取出了其中一個比較特殊的狀態來研究,限于篇幅(以及本文主題是canvas而非物理),沒有推廣到更一般的結論,但其實,這些分析已經足夠了,無論是位移還是速度,他都是矢量,帶有方向,那么我們不妨規定:以canvas的坐標軸,數值增加的方向為正向,那么從右邊拋出,可以認為是反向,可以表示為 -v0 ,最終通過計算位移的公式,可以得到正確的坐標(但這時候算坐標x是比較麻煩的,不能直接使用上述公式)。
分析這么多,說點兒咱最關心的實現。
在之前的分析中,我們知道想求小球任意時刻所在位置坐標,需要的參數有:v0, t, g, vh。這些參數應該存放在哪里呢?怎么設計這個數據結構?
我們當然可以直接將這些參數設為全局變量,但這顯然是不合適的,這些參數里,唯一適合設為全局變量的是重力加速度g。而v0, t, vh這些都應該是小球自身的“屬性”,所以我們應該將其抽象成一個類。
function Ball(r, v0, vh, t) {
this.r = r;
this.v0 = v0;
this.vh = vh;
this.t = t;
this.x = 0;
this.h = 0;
this.calcX = function() { /* 計算水平位移 */ }
this.calcH = function() { /* 計算豎直位移 */ }
}
var ball = { x: 0, h: 0, r: 10, v0: 0, vh: 0, g: 10};
// 重力加速度無論是作為全局變量還是小球屬性,均可
// es6之后
class Ball {
constructor();
}
以上三種方式,各有各的好處,選擇一個合適的方式即可。
“你這說物理我就頭大,有沒有更簡單的?”
更簡單也有啊,反正并沒有要求100%還原物理學場景:
var ball = { x: 0, y: 0, r: 10, vx: 5, vy: 0, g: 5 };
setInterval(() => {
ball.vy += ball.g; // 豎直方向速度增加
ball.y += ball.vy; // 豎直方向位移
ball.x += ball.vx; // 水平方向位移
cxt.clearRect(0, 0, 800, 300);
cxt.beginPath();
cxt.fillStyle = 'black';
cxt.arc(ball.x, ball.y, ball.r, 0, 2*Math.PI);
cxt.fill();
}, 50);
OK,結束了。
這就是高級一點的動畫。可能在學幾個函數,這個動畫會更炫一點。比如學完矩形填充再掌握一點rgba的知識,你可以做個“尾巴”出來,即長尾效應。具體只需要將上述代碼中的 cxt.clearRect() 替換成:
cxt.fillStyle = 'rgba(255, 255, 255, 0.2)';
cxt.fillRect(0, 0, 800, 300);
這就能顯得咱們編碼能力很厲害的樣子。
做到這一步還是不滿足:小球一個勁兒的向下掉,這動畫沒一會兒就沒了。
沒關系,咱們可以做“ 碰撞檢測 ”啊。好像又是一個高大上的詞匯,但實際上也沒什么高大上的,如果基于本節第一部分的分析,那咱還得考慮一下碰撞造成的動量損失的問題,挺復雜的。
但是簡化版就好說了啊。小球碰到上/下邊界,豎直方向速度反向,同時速率減半。左右邊界可以有類似的處理。
if (ball.r + ball.x > canvas_width) {
ball.vx *= -0.5
}
if (ball.r + ball.y > canvas_height) {
ball.vy *= -0.5;
}
NOTE:碰撞檢測在這里指的是“ 邊界檢測 ”,小球落到邊界的時候再繼續下落顯然是沒有意義的,因為后面的動畫咱們是看不到的。所以要么碰到邊界就停止,要么重新開始,或者進行其他處理,總之,不能出現無意義的動畫。
像以前玩的貪吃蛇,會有各種墻的存在,控制的小蛇在碰到墻的時候,游戲就失敗了,或者說沒有墻的時候,小蛇會從另一個方向出來。
說了這么多,你會發現,本文不僅沒有直接的羅列不同的DEMO來介紹函數,更是在盡量避免過多的介紹canvas中的API。
個人看來,canvas其實就是一個函數庫,他和我們平時使用的那些什么forEach,splice,split,map,reduce沒什么區別,都是封裝好了直接用的,查一查函數手冊就可以了解用法了,多用幾次就會比較熟悉了。
剛進大學的時候,專業課老師就告訴我們,程序=算法+數據結構,即使到現在,也有很多人在強調這一點。如果你有心,再回想一下上一節內容,在分析平拋運動的時候,我本質上是在考慮算法問題;在設計小球的類時,考慮了面向對象,但更多的是在考慮數據結構的問題,在考慮了這些內容的基礎上,我才開始了具體的實現。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。