整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          使用 canvas 繪制網(wǎng)格背景

          有沒有很好奇,類似 processOn 這類作圖網(wǎng)站中,網(wǎng)格背景 是怎么做的呢?

          網(wǎng)格背景

          如果你 F12 看過它的代碼,你將發(fā)現(xiàn)原來不是通過 background-image 之類 css 屬性做的,而是通過 canvas 實現(xiàn)的。

          網(wǎng)格背景

          這篇主要講如何通過 canvas 來繪制出這樣的 網(wǎng)格背景,以及中間碰到的一個 線條模糊的問題

          手把手寫代碼

          1. 創(chuàng)建 ctx 對象

          這邊將新建一個和屏幕尺寸相同的 canvas 畫布:

          const canvasEl = document.createElement("canvas");
          const sh = screen.height;
          const sw = screen.width;
          canvasEl.width = sw;
          canvasEl.height = sh;
          const ctx = canvasEl.getContext("2d");

          2. 繪制網(wǎng)格

          先簡單說下思路:

          根據(jù) canvas 畫布尺寸,以 10px 為間距,分別繪制橫縱坐標的線條。

          比如繪制橫向線條,先將“畫筆”移至起點坐標 (0, y),然后通過線條方法 lineTo 繪制屏幕寬度的線條,即繪制 (0, y) 至 (screen.width, y) 的直線。

          然后 y 會按照間距 10px 逐漸遞增,直至繪制完整個屏幕。

          下面根據(jù)橫縱兩個方向,封裝了 draw 方法:

          const draw = function (isColumn) {
            const gutter = 10;
            const limit = isColumn ? sh : sw;
          
            let i = 0;
            while (i * gutter + gutter <= limit) {
              i++;
              const point = i * gutter;
          
              // 清空子路徑列表開始一個新路徑
              ctx.beginPath();
          
              // 分割線
              ctx.strokeStyle = point % 100 !== 0 ? "#f0f0f0" : "#d6e4ff";
          
              // 將一個新的子路徑的起始點移動到(x,y)坐標
              if (isColumn) {
                ctx.moveTo(0, point);
              } else {
                ctx.moveTo(point, 0);
              }
              // 使用直線連接子路徑的終點到x,y坐標
              if (isColumn) {
                ctx.lineTo(sw, point);
              } else {
                ctx.lineTo(point, sh);
              }
              // 根據(jù)當前的畫線樣式,繪制當前或已經(jīng)存在的路徑的方法
              ctx.stroke();
            }
          };

          然后分別繪制橫縱坐標,并把 canvas 掛載到 body 上:

          draw(true);
          draw(false);
          
          document.body.append(canvasEl);

          很順利你將得到 canvas 繪制的網(wǎng)格圖形:

          網(wǎng)格背景

          線條模糊

          如果你觀察比較細膩,能發(fā)現(xiàn)上面的網(wǎng)格圖形并不是很清晰,可以仔細觀察下面的圖:

          對比效果(左邊模糊,右邊清晰)

          為什么會有這樣的情況出現(xiàn)呢?

          因為調(diào)用 lineTo 是從 A 點到 B 點,軌跡是點到點的連線。當繪制 1px 線條時,是以這個軌跡連線為中間線左右各渲染 0.5px 的線條。這會使得一個像素點只渲染了一半,而另一半會用一個比較弱的顏色填充,導致繪制出模糊的線條。

          怎么改?

          只要在 moveTo 時,將坐標偏移 0.5px(即將線條軌跡偏移 0.5 位置),然后調(diào)用 lineTo 時,也將坐標偏移 0.5px。

          這樣,最終繪制線條的時候,將在一個完整的像素區(qū)域進行渲染了。

          選自:https://www.jianshu.com/p/c0970eecd843

          有沒有便捷的方法?

          使用 translate 偏移 x 和 y 坐標值,然后繪制好后再調(diào)用 setTransform 重置回來:

          ctx.translate(0.5, 0.5);
          //...
          ctx.setTransform(1, 0, 0, 1, 0, 0);

          參考

          • canvas 畫布解決 1px 線條模糊問題
          • Drawing a 1px thick line in canvas creates a 2px thick line
          • CanvasRenderingContext2D.setTransform()

          #前端開發(fā)#

          么是Canvas

          <canvas> 是HTML中的一個元素,它可被用來通過 JavaScript(Canvas API 或 WebGL API)繪制圖形及圖形動畫。

          Canvas API 提供了一個通過 JavaScriptHTML<canvas> 元素來繪制圖形的方式。它可以用于動畫、游戲畫面、數(shù)據(jù)可視化、圖片編輯以及實時視頻處理等方面。

          <canvas>標簽本身沒有繪圖能力,它僅僅是圖形的容器。在HTML,一般通過Javascript語言來完成實際的操作。

          創(chuàng)建HTML頁面并添加繪圖容器

          本文通過Javascript操作Canvas制作一個簡單的顯示當前時間的動畫時鐘,了解和學習簡單的canvas用法,僅以拋磚引玉。

          首先創(chuàng)建一個HTML文件,為了方便管理,使用一個div標簽包裹兩個canvas標簽,并加上一些簡單的css樣式。

          <!doctype html>
          <html lang="zh-cn">
          <head><title>Canvas繪制動畫時鐘</title>
          <style>
          html,body{margin:0;padding:0}
          #clockWrap {
          	position: relative;
          }
          canvas {
          	position: absolute;
          }
          #clock-ui {
          	z-index: 2;
          }
          #clock-plate {
          	z-index: 1;
          }
          </style>
          </head>
          <body>
            <div id="clockWrap">
            <canvas id="clock-plate"></canvas>
            <canvas id="clock-ui"></canvas>
          </div>
          <script></script>
          </body></html>

          本示例中使用了兩個canvas標簽(為什么使用兩個,一個不是更簡單嗎?),一個用于繪制鐘面,一個根據(jù)當前時間實時顯示和更新時針、分針和秒針的動態(tài)指向。好了,話不多說,開干。

          繪制鐘面刻度

          一個簡單的時鐘,可以分為鐘面上的刻度和指針。其中刻度和12個數(shù)字是固定的,我們可以將它們繪制在當作背景的canvas上(示例中id為clock-plate的canvas)。

          (1)要使用canvas,首先必須通過容器獲取渲染上下文:

          var $=function(id){return document.querySelector(id);}//這個函數(shù)只是為了方便獲取dom元素
          const canvasbg=$("#clock-plate"),
                canvas=$("#clock-ui"),
                ctx = canvasbg.getContext("2d"),//背景容器上下文
                ctxUI = canvas.getContext("2d");//指針容器上下文,后面代碼要用
          //定義畫布寬度和高度,時鐘圓直徑,并設(shè)置畫布大小
          const oW=1000,oH=800,cW=400,r=cW/2,oX=oW/2,oY=oH/2;
          canvas.width=oW;
          canvas.height=oH;
          canvasbg.width=oW;
          canvasbg.height=oH;

          (2)畫鐘的邊框,為了好看,這里畫兩個圈:

           //畫出時鐘外圓框
            ctx.lineWidth = 12;
          	ctx.beginPath();
          	ctx.arc(oX, oY, r+14, 0, 2 * Math.PI);
          	ctx.stroke();
          	ctx.closePath();
          	ctx.lineWidth = 8;
          	//畫出時鐘內(nèi)圓框(刻度圈)
          	ctx.beginPath();
          	ctx.arc(oX, oY, r, 0, 2 * Math.PI);
          	ctx.stroke();
          	ctx.closePath();
          	ctx.beginPath();

          邊框效果圖

          (3)繪制刻度線和數(shù)字,可以利用三角函數(shù)計算出每個刻度的坐標:

          利用三角函數(shù)計算刻度線的坐標位置

          鐘面上有12個大格,從正上方12開始,它們的度數(shù)分別是270,300,330,0,30,60,90,120,150,180,210,240。然后利用JS的Math.sin和Math.cos分別計算出各大格的坐標。注意:js中Math.sin()和Math.cos()的參數(shù)不是角度數(shù)弧度。可以使用Math.PI/180*角度來轉(zhuǎn)化,比如將30度轉(zhuǎn)換成弧度=Math.PI/180*30

          //繪制鐘表中心點
          	ctx.beginPath();
          	ctx.arc(oX, oY, 8, 0, 2 * Math.PI);//圓心
          	ctx.fill();
          	ctx.closePath();
          	//設(shè)置刻度線粗細度
          	ctx.lineWidth = 3;
          	//設(shè)置鐘面12個數(shù)字的字體、大小和對齊方式
          	ctx.font = "30px serif";
          	ctx.textAlign="center";
          	ctx.textBaseline="middle";
          	var kdx,kdy;
          	//繪制12個大刻度和12個數(shù)字
          	//為方便計算,先定義了0-11這12個刻度對應的度數(shù),也可以直接定義對應的弧度。
          	const hd=Math.PI/180,degr=[270,300,330,0,30,60,90,120,150,180,210,240];
          	for(var i=0;i<12;i++){
          		kdx=oX+Math.cos(hd*degr[i])*(r-3);
          		kdy=oY+Math.sin(hd*degr[i])*(r-3);
          		ctx.beginPath();
          		ctx.arc(kdx, kdy, 6, 0, 2 * Math.PI);//畫圓形大刻度
          		ctx.fill();
              //繪制刻度對應的數(shù)字
          		ctx.strokeText(i==0? 12 : i,oX+Math.cos(hd*degr[i])*(r-24),oY+Math.sin(hd*degr[i])*(r-24));
          		ctx.closePath();
          	}
          	
          	//繪制小刻度
          	ctx.lineWidth = 2;
          	for(var i=0;i<60;i++){
          		if(i % 5 == 0) continue;//跳過與刻度重疊的刻度
          		x0=Math.cos(hd*i*6);
          		y0=Math.sin(hd*i*6);
          		ctx.beginPath();
          		ctx.moveTo(oX+x0*(r-10), oY+y0*(r-10)); 
          		ctx.lineTo(oX+x0*r, oY+y0*r); //畫短刻度線
          		ctx.stroke(); 
          		ctx.closePath();
          	}

          效果如圖:

          鐘面效果圖

          (4)根據(jù)當前時間繪制指針

          習慣上,時針粗短,分針略粗而長,秒針細長。為加大區(qū)別,示例中秒針細長并且繪制成紅色。

          function drawHp(i){//繪制時針
          	const x0=Math.cos(hd*i*30),y0=Math.sin(hd*i*30);
          	drawPointer(oX,oY,oX+x0*(r-90),oY+y0*(r-90),10,"#000000");
          }
          function drawMp(i){//繪制分針
          	const x0=Math.cos(hd*i*6),y0=Math.sin(hd*i*6);
          	drawPointer(oX,oY,oX+x0*(r-60),oY+y0*(r-60),5,"#000000");
          }
          function drawSp(i){//繪制秒針
          	const x0=Math.cos(hd*i*6),y0=Math.sin(hd*i*6);
          	drawPointer(oX,oY,oX+x0*(r-20),oY+y0*(r-20),2,"#FF0000");
          }
          //抽取出繪制三種指針時共同的部分,注意指針繪制在渲染上下文ctxUI中
          function drawPointer(ox,oy,tx,ty,width,color){
          	ctxUI.strokeStyle = color;
          	ctxUI.lineCap = "round";
          	ctxUI.lineWidth = width;
          	ctxUI.beginPath();
          	ctxUI.moveTo(ox, oy);
          	ctxUI.lineTo(tx,ty);
          	ctxUI.stroke();
          	ctxUI.closePath();
          }

          現(xiàn)在已經(jīng)有了繪制三種指針的方法,參數(shù)是當前時間的時、分和秒,將根據(jù)它們的值確定指針的坐標。不過,因為使用的是默認的convas坐標體系,0值實際指向3的位置,需要小小的修正一下。

          window.requestAnimationFrame(function fn(){
          		var d = new Date();
          		ctxUI.clearRect(0,0,oW,oH);
          		//度數(shù)從0開始,而0在3刻度(15分/秒位置),修正為全值減15,如果小于0則修正回來
              var hour=d.getHours(),minute=d.getMinutes()-15,second=d.getSeconds()-15;
          		hour=hour>11? hour-15 : hour-3;
          		drawHp(hour>=0? hour : 12+hour);
          		drawMp(minute>=0? minute : 60+minute);
          		drawSp(second>=0? second : 60+second);
          		window.requestAnimationFrame(fn);
          });

          接下來,調(diào)用window.requestAnimationFrame,在其中繪制并更新指標的位置。看看效果如何:

          指針繪制情況與實際時間相符

          貌似效果有了,截圖時電腦上的時間是10時17分,指針繪制上,時針指向10時,分針指向17。嗯,感覺有點別扭?對了,時針和分針怎么是端端正正地指向它們的整時整分刻度上呢?實際鐘表上時針和分針是展示動態(tài)進度的,此時時針應該越過10時的位置才對。沒關(guān)系,我們在繪制時針和分針時別點東西,讓它的角度值加上分針和秒針的值試試。

          //修改后的繪制三種指針的方法
          function drawHp(i,f,m){//繪制時針,參數(shù):時,分,秒
          	const x0=Math.cos(hd*(i+(f/60)+(m/600))*30),y0=Math.sin(hd*(i+(f/60)+(m/600))*30);
          	drawPointer(oX,oY,oX+x0*(r-90),oY+y0*(r-90),10,"#000000");
          }
          function drawMp(i,f){//繪制分針,參數(shù),分,秒
          	const x0=Math.cos(hd*(i+(f/60))*6),y0=Math.sin(hd*(i+(f/60))*6);
          	drawPointer(oX,oY,oX+x0*(r-60),oY+y0*(r-60),5,"#000000");
          }
          function drawSp(i){//繪制秒針
          	const x0=Math.cos(hd*i*6),y0=Math.sin(hd*i*6);
          	drawPointer(oX,oY,oX+x0*(r-20),oY+y0*(r-20),2,"#FF0000");
          }

          再來看看效果,嗯,立竿見影呀:

          指針指向更合理了

          到此為止,canvas繪制一個簡易時鐘就完成了。下面繼續(xù)優(yōu)化一下。剛才使用requestAnimationFrame方法即時更新繪制情況。這個方法與刷新率有關(guān),看看mdn上面怎么說的:

          window.requestAnimationFrame() 方法會告訴瀏覽器你希望執(zhí)行一個動畫。它要求瀏覽器在下一次重繪之前,調(diào)用用戶提供的回調(diào)函數(shù)。

          對回調(diào)函數(shù)的調(diào)用頻率通常與顯示器的刷新率相匹配。雖然 75hz、120hz 和 144hz 也被廣泛使用,但是最常見的刷新率還是 60hz(每秒 60 個周期/幀)。為了提高性能和電池壽命,大多數(shù)瀏覽器都會暫停在后臺選項卡或者隱藏的 <iframe> 中運行的 requestAnimationFrame()。

          本示例中,更新指針的位置并不需要很高的刷新頻率,可以通過節(jié)流進行一下優(yōu)化:

          var fps = 5, fpsInterval = 1000 / fps,lastTime = new Date().getTime(); //記錄上次執(zhí)行的時間
          function runStep() {
              requestAnimationFrame(runStep);
              var d=new Date(),now = d.getTime()
              var elapsed = now - lastTime;
              if (elapsed > fpsInterval) {
          				ctxUI.clearRect(0,0,oW,oH);
                  lastTime = now - (elapsed % fpsInterval); 
          			//度數(shù)從0開始,而0在3刻度(15分/秒位置),修正為全值-15,如果小于0則用60減回
                  var hour=d.getHours(),minute=d.getMinutes()-15,second=d.getSeconds()-15;//console.log(d.getSeconds(),second);
                  hour=hour>11? hour-15 : hour-3;
                  drawHp(hour>=0? hour : 12+hour,minute+15,second+15);
                  drawMp(minute>=0? minute : 60+minute,second+15);
                  drawSp(second>=0? second : 60+second);
              }
          }
          runStep();

          當然,實現(xiàn)時鐘的方法是很多,比如可以使用畫布的旋轉(zhuǎn)(rotate方法)來實現(xiàn)指針的動態(tài)轉(zhuǎn)動等等。

          完整HTML+JS源碼:

          隨著HTML5的火熱,越來越多的人投入到HTML5開發(fā)中了,canvas作為HTML5中比較重要的一個元素,在很多官網(wǎng)的主頁面中被使用到。今天我們一起來看看如何使用canvas畫出一個夢幻的星空背景,還會有流星運動。

          本文的代碼已經(jīng)放到Github上了,感興趣的可以自取,Github地址如下。

          https://github.com/zhouxiongking/article-pages/blob/master/articles/starry/starry.html

          HTML5

          實現(xiàn)效果

          首先我們來看看通過canvas實現(xiàn)的星空效果圖,如下所示。

          效果圖

          代碼實現(xiàn)

          接下來我們看看這個效果是如何通過代碼一步步實現(xiàn)的。

          首先來看看頁面上的HTML代碼,只有一個Div元素。

          HTML代碼

          Javascript代碼

          首先我們需要定義一些常量,比如畫布的寬和高,星星數(shù)量,流星個數(shù)。在這個星空中流星其實是星星的一個,只是添加了動態(tài)效果。

          頁面初始化

          然后是設(shè)定一個定時器,通過一段隨機時間生成一個流星的索引號。

          流星索引號

          緊接著來看看生成一個星星的方法,該方法返回一個星星的各項參數(shù),包括x,y軸坐標,透明度,x,y軸偏移量。

          生成星星的參數(shù)

          然后是最重要的render方法,通過該方法可以將星星渲染至畫布上,我們將這個方法拆開看,首先是對流星的繪制,流星索引號通過上面metor方法獲得。

          畫流星

          然后是對于星星各項參數(shù)的處理,比如有的星星生成的點坐標超出了屏幕寬高,有的透明度是負數(shù),都要將其處理成正常參數(shù)。

          各項參數(shù)判斷

          最后是在畫布中進行繪制。

          畫布繪制

          至此,這個畫面效果的講解完畢,如果代碼正確的話,就可以看到文中出現(xiàn)的效果圖。

          結(jié)束語

          今天這篇文章主要是借助HTML5中的canvas畫出了一個夢幻星空的效果,你學會了嗎?


          主站蜘蛛池模板: 亚洲一区爱区精品无码| AV无码精品一区二区三区宅噜噜| 亚洲国产精品一区二区第四页| 无码少妇一区二区| 精品一区狼人国产在线| 久久精品国产一区二区三区| 亚洲一区二区三区精品视频| 国内国外日产一区二区| 午夜性色一区二区三区不卡视频| 一区二区三区国产精品| 国产精品乱码一区二区三区| 精品日韩一区二区| 亚洲福利一区二区| 久久免费国产精品一区二区| 一区二区三区四区在线播放 | 亚洲AV无码片一区二区三区 | 无码视频一区二区三区| 久久一区二区三区精品| 国产一区在线视频| 动漫精品一区二区三区3d| 69久久精品无码一区二区| 无码人妻精品一区二区三区在线 | 亚洲AⅤ无码一区二区三区在线| 国产99视频精品一区| 一区二区三区在线免费看| 自慰无码一区二区三区| 国产福利电影一区二区三区,亚洲国模精品一区 | 亚洲日本一区二区一本一道 | 久久精品亚洲一区二区三区浴池 | 日韩精品一区二区三区色欲AV | 538国产精品一区二区在线| 日韩精品久久一区二区三区| 在线精品亚洲一区二区| 精品国产乱码一区二区三区| 中文字幕一区二区三区久久网站| 国产精品无码一区二区三级| 国产成人一区二区三区在线| 国产av夜夜欢一区二区三区| 在线播放精品一区二区啪视频| 日韩精品一区二区三区老鸭窝| 人妻无码一区二区三区免费|