[js高手之路]html5 canvas動畫教程 - 自己動手做一個類似windows的畫圖軟體

来源:http://www.cnblogs.com/ghostwu/archive/2017/10/17/7684379.html
-Advertisement-
Play Games

這個繪圖工具,我還沒有做完,不過已經實現了總架構,以及常見的簡易圖形繪製功能: 1,可以繪製直線,圓,矩形,正多邊形【已完成】 2,填充顏色和描邊顏色的選擇【已完成】 3,描邊和填充功能的選擇【已完成】 後續版本: 橡皮擦,坐標系,線形設置,箭頭,其他流程圖形,裁剪與調整圖形。。。。。 終極目標: ...


這個繪圖工具,我還沒有做完,不過已經實現了總架構,以及常見的簡易圖形繪製功能:

1,可以繪製直線,圓,矩形,正多邊形【已完成】

2,填充顏色和描邊顏色的選擇【已完成】

3,描邊和填充功能的選擇【已完成】

後續版本:

橡皮擦,坐標系,線形設置,箭頭,其他流程圖形,裁剪與調整圖形。。。。。

終極目標:

流程繪製軟體

我是之前看見一位朋友在我的博客中留言說:

非常感謝這個朋友,今天終於抽出時間完成非常非常小的雛形!

完整的雛形代碼,請自行打開,複製到本地測試.

  1 <head>
  2     <meta charset="UTF-8">
  3     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  4     <meta http-equiv="X-UA-Compatible" content="ie=edge">
  5     <title>windows簡易畫圖工具 - by ghostwu</title>
  6 </head>
  7 
  8 <body>
  9     <div class="paint">
 10         <div class="paint-header">
 11             <ul>
 12                 <li class="active">形狀</li>
 13                 <li>顏色</li>
 14                 <li>繪製類型</li>
 15                 <li>線條寬度</li>
 16                 <li>橡皮擦</li>
 17             </ul>
 18         </div>
 19         <div class="paint-body">
 20             <div class="siderbar">
 21                 <div class="item active" data-type="paint-shape">
 22                     <ul>
 23                         <li class="active" data-role="line">線條</li>
 24                         <li data-role="circle">圓形</li>
 25                         <li data-role="rect">矩形</li>
 26                         <li data-role="polygon">正多邊形</li>
 27                         <li data-role="arrow">箭頭</li>
 28                     </ul>
 29                 </div>
 30                 <div class="item" data-type="paint-color">
 31                     <ul>
 32                         <li data-role="strokeStyle">
 33                             <input type="color" data-role="strokeStyle">
 34                         </li>
 35                         <li data-role="fillStyle">
 36                             <input type="color" data-role="fillStyle">
 37                         </li>
 38                     </ul>
 39                 </div>
 40                 <div class="item" data-type="paint-type">
 41                     <ul>
 42                         <li data-role="stroke">描邊</li>
 43                         <li data-role="fill">填充</li>
 44                     </ul>
 45                 </div>
 46                 <div class="item" data-type="paint-line">
 47                     <ul>
 48                         <li data-role="1">小號</li>
 49                         <li data-role="4">中號</li>
 50                         <li data-role="7">大號</li>
 51                         <li>
 52                             <input type="number" data-role="line-size" placeholder="請輸入數字">
 53                         </li>
 54                     </ul>
 55                 </div>
 56                 <div class="item" data-type="paint-erase">
 57                     <ul>
 58                         <li>
 59                             <input type="number" data-role="erase-size" placeholder="請輸入數字">
 60                         </li>
 61                     </ul>
 62                 </div>
 63             </div>
 64         </div>
 65     </div>
 66     <script>// <![CDATA[
 67         var oPaintBody = document.querySelector( '.paint-body' );
 68         var oC = document.createElement( 'canvas' );
 69         oC.setAttribute( 'width', '830' );
 70         oC.setAttribute( 'height', '500' );
 71         oPaintBody.appendChild( oC );
 72         var aHeaderLi = document.querySelectorAll('.paint-header li'),
 73             aItem = document.querySelectorAll('.paint-body .item'),
 74             oCanvas = document.querySelector('.paint canvas'),
 75             oGc = oCanvas.getContext('2d'),
 76             cWidth = oCanvas.width, cHeight = oCanvas.height,
 77             curItem = aItem[0],
 78             aItemLi = curItem.querySelectorAll('li');
 79 
 80         for (let i = 0, len = aHeaderLi.length; i < len; i++) { //頭部選項卡切換功能
 81             aHeaderLi[i].onclick = function () {
 82                 for (let j = 0; j < len; j++) {
 83                     aHeaderLi[j].classList.remove('active');
 84                     aItem[j].style.display = 'none';
 85                 }
 86                 aItem[i].style.display = "block";
 87                 this.classList.add('active');
 88                 curItem = aItem[i];
 89                 aItemLi = curItem.querySelectorAll('li');
 90                 activeItem(aItemLi);
 91             }
 92         }
 93         activeItem(aItemLi);
 94         var role = null;
 95         function activeItem(aItemLi) { //canvas左側選項卡切換功能
 96             for (let i = 0, len = aItemLi.length; i < len; i++) {
 97                 aItemLi[i].onclick = function () {
 98                     checkPaintType(this); //繪製類型
 99                     for (let j = 0; j < len; j++) {
100                         aItemLi[j].classList.remove('active');
101                     }
102                     this.classList.add('active');
103                 }
104             }
105         }
106 
107         function Shape(canvasObj, cxtObj, w, h) {
108             this.oCanvas = canvasObj;
109             this.oGc = cxtObj;
110             this.oCanvas.width = w;
111             this.oCanvas.height = h;
112             this.fillStyle = '#000';
113             this.storkeStyle = '#000';
114             this.lineWidth = 1;
115             this.drawType = 'line';
116             this.paintType = 'stroke';
117             this.nums = 6; //正多邊形的邊數
118         }
119 
120         Shape.prototype = {
121             init: function () {
122                 this.oGc.fillStyle = this.fillStyle;
123                 this.oGc.strokeStyle = this.strokeStyle;
124                 this.oGc.lineWidth = this.lineWidth;
125             },
126             draw: function () {
127                 var _this = this;
128                 this.oCanvas.onmousedown = function (ev) {
129                     _this.init();
130                     var oEvent = ev || event,
131                         startX = oEvent.clientX - _this.oCanvas.offsetLeft,
132                         startY = oEvent.clientY - _this.oCanvas.offsetTop;
133                     _this.oCanvas.onmousemove = function (ev) {
134                         _this.oGc.clearRect(0, 0, _this.oCanvas.width, _this.oCanvas.height);
135                         var oEvent = ev || event,
136                             endX = oEvent.clientX - _this.oCanvas.offsetLeft,
137                             endY = oEvent.clientY - _this.oCanvas.offsetTop;
138                         _this[_this.drawType](startX, startY, endX, endY);
139                     };
140                     _this.oCanvas.onmouseup = function () {
141                         _this.oCanvas.onmousemove = null;
142                         _this.oCanvas.onmouseup = null;
143                     }
144                 }
145             },
146             line: function (x1, y1, x2, y2) {
147                 this.oGc.beginPath();
148                 this.oGc.moveTo(x1, y1);
149                 this.oGc.lineTo(x2, y2);
150                 this.oGc.closePath();
151                 this.oGc.stroke();
152             },
153             circle: function (x1, y1, x2, y2) {
154                 this.oGc.beginPath();
155                 var r = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
156                 this.oGc.arc(x1, y1, r, 0, 2 * Math.PI, false);
157                 this.oGc.closePath();
158                 this.oGc[this.paintType]();
159             },
160             rect: function (x1, y1, x2, y2) {
161                 this.oGc.beginPath();
162                 this.oGc.rect(x1, y1, x2 - x1, y2 - y1);
163                 this.oGc[this.paintType]();
164             },
165             polygon: function (x1, y1, x2, y2) {
166                 var angle = 360 / this.nums * Math.PI / 180;//邊對應的角的弧度
167                 var r = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
168                 this.oGc.beginPath();
169                 for (var i = 0; i < this.nums; i++) {
170                     this.oGc.lineTo(x1 + r * Math.cos(angle * i), y1 + r * Math.sin(angle * i));
171                 }
172                 this.oGc.closePath();
173                 this.oGc[this.paintType]();
174             }
175         }
176 
177         var oShape = new Shape(oCanvas, oGc, cWidth, cHeight);
178         function checkPaintType(liType) {
179             var dataType = liType.parentNode.parentNode.dataset.type;
180             var curType = liType.dataset.role;
181             switch (dataType) {
182                 case 'paint-shape': //形狀
183                     oShape.drawType = curType;
184                     if (curType == 'polygon') {
185                         oShape.nums = prompt("請輸入邊數", 6);
186                     }
187                     oShape.draw();
188                     break;
189                 case 'paint-color': //繪製顏色
190                     liType.children[0].onchange = function () {
191                         oShape[this.dataset.role] = this.value;
192                     }
193                     oShape.draw();
194                     break;
195                 case 'paint-type': //繪製類型
196                     oShape.paintType = curType;
197                     oShape.draw();
198                     break;
199             }
200         }
201 // ]]></script>
202     <style>
203         .paint * {
204             margin: 0;
205             padding: 0;
206         }
207 
208         .paint ul,
209         .paint li {
210             list-style: none;
211         }
212 
213         .paint li:hover {
214             cursor: pointer;
215         }
216 
217         .paint {
218             width: 980px;
219             margin: 20px auto;
220             border: 1px solid #ccc;
221             overflow: hidden;
222         }
223 
224         .paint .paint-header ul {
225             width: 980px;
226             height: 40px;
227             line-height: 40px;
228             border-bottom: 1px solid #ccc;
229         }
230 
231         .paint .paint-header li {
232             float: left;
233             width: 120px;
234             height: 40px;
235             line-height: 40px;
236             text-align: center;
237         }
238 
239         .paint li.active {
240             box-shadow: #666 0px 1px 8px inset;
241         }
242 
243         .paint .paint-body .siderbar {
244             float: left;
245             width: 150px;
246             height: 500px;
247         }
248 
249         .paint .paint-body .item {
250             width: 150px;
251             overflow: hidden;
252             display: none;
253             height: 500px;
254             border-right: 1px solid #ccc;
255         }
256 
257         .paint .paint-body canvas {
258             float: right;
259         }
260 
261         .paint .paint-body .item li {
262             height: 40px;
263             text-align: center;
264             border-bottom: 1px solid #ccc;
265             line-height: 40px;
266         }
267 
268         .paint .paint-body .active {
269             display: block;
270         }
271     </style>
272 </body>
View Code

關於流程設計,後期要做的功能,思路基本上已經有了,好了,圓規正傳,想要完成這個終極目標,完成一個畫圖工具應該就能接近目標了。先體驗下目前的簡易功能,下麵是可以正常畫圖的,【需要你的瀏覽器支持canvas才可以額

  • 形狀
  • 顏色
  • 繪製類型
  • 線條寬度
  • 橡皮擦
  • 線條
  • 圓形
  • 矩形
  • 正多邊形
  • 箭頭
  • 描邊
  • 填充
  • 小號
  • 中號
  • 大號

 

主要來講下目標的雛形架構:

1,圖形繪製部分,我封裝了一個類Shape

 1 function Shape(canvasObj, cxtObj, w, h) {
 2         this.oCanvas = canvasObj;
 3         this.oGc = cxtObj;
 4         this.oCanvas.width = w;
 5         this.oCanvas.height = h;
 6         this.fillStyle = '#000';
 7         this.storkeStyle = '#000';
 8         this.lineWidth = 1;
 9         this.drawType = 'line';
10         this.paintType = 'stroke';
11         this.nums = 6; //正多邊形的邊數
12     }

canvasObj: 就是canvas畫布對象

cxtObj: 就是上下文繪圖環境

w: canvas的寬度

h:  canvas的高度

fillStyle: 填充顏色

strokeStyle: 描邊顏色

lineWidth: 線寬

drawType: 預設為畫直線

paintType: stroke/fill 兩種選擇( 描邊/填充)

2,在原型對象上擴展一個公共方法draw用來繪製圖形

draw方法,主要獲取起始點坐標(startX, startY),以及終點坐標( endX, endY );

然後調用init方法來獲取繪製狀態,繪製具體的圖形靠下麵這個關鍵方法:

_this[_this.drawType](startX, startY, endX, endY)

這個方法的drawType會根據界面的實時選擇,變換對應的繪製類型,如:

_this['line']( startX, startY, endX, endY )

調用的就是oShape對象中的line,畫直線的方法

 1 Shape.prototype = {
 2         init: function () {
 3             this.oGc.fillStyle = this.fillStyle;
 4             this.oGc.strokeStyle = this.strokeStyle;
 5             this.oGc.lineWidth = this.lineWidth;
 6         },
 7         draw: function () {
 8             var _this = this;
 9             this.oCanvas.onmousedown = function ( ev ) {
10                 _this.init();
11                 var oEvent = ev || event,
12                     startX = oEvent.clientX - _this.oCanvas.offsetLeft,
13                     startY = oEvent.clientY - _this.oCanvas.offsetTop;
14                 _this.oCanvas.onmousemove = function ( ev ) {
15                     _this.oGc.clearRect( 0, 0, _this.oCanvas.width, _this.oCanvas.height );
16                     var oEvent = ev || event,
17                         endX = oEvent.clientX - _this.oCanvas.offsetLeft,
18                         endY = oEvent.clientY - _this.oCanvas.offsetTop;
19                     _this[_this.drawType](startX, startY, endX, endY);
20                 };
21                 _this.oCanvas.onmouseup = function(){
22                     _this.oCanvas.onmousemove = null;
23                     _this.oCanvas.onmouseup = null;
24                 }
25             }
26         },
27         line: function ( x1, y1, x2, y2 ) {
28             this.oGc.beginPath();
29             this.oGc.moveTo( x1, y1 );
30             this.oGc.lineTo( x2, y2 );
31             this.oGc.closePath();
32             this.oGc.stroke();
33         },
34         circle : function( x1, y1, x2, y2 ){
35             this.oGc.beginPath();
36             var r = Math.sqrt( Math.pow( x2 - x1, 2 ) + Math.pow( y2 - y1, 2 ) );
37             this.oGc.arc( x1, y1, r, 0, 2 * Math.PI, false );
38             this.oGc.closePath();
39             this.oGc[this.paintType]();
40         },
41         rect : function( x1, y1, x2, y2 ){
42             this.oGc.beginPath();
43             this.oGc.rect( x1, y1, x2 - x1, y2 - y1 );
44             this.oGc[this.paintType]();
45         },
46         polygon : function( x1, y1, x2, y2 ){
47             var angle = 360 / this.nums * Math.PI / 180;//邊對應的角的弧度
48             var r = Math.sqrt( Math.pow( x2 - x1, 2 ) + Math.pow( y2 - y1, 2 ) );
49             this.oGc.beginPath();
50             for( var i = 0; i < this.nums; i ++ ){
51                 this.oGc.lineTo( x1 + r * Math.cos( angle * i ), y1 + r * Math.sin( angle * i ) );
52             }
53             this.oGc.closePath();
54             this.oGc[this.paintType]();
55         }
56     }

3,界面操作很簡單,基本是選項卡的操作+html5的自定義屬性+classList的應用

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 說到記憶體管理,筆者這裡想先比較一下java與C、C++之間的區別: 在C、C++中,記憶體管理是由程式員負責的,也就是說程式員既要完成繁重的代碼編寫工作又要時常考慮到系統記憶體的維護 在java中,程式員無需考慮記憶體的控制和維護,而是交由JVM自動管理,這樣就不容易出現記憶體泄漏和溢出的問題。然而,一旦出 ...
  • random是內置模塊,使用時直接import random random.random() --> 生成一個0到1的隨機符點數: 0 <= n < 1.0 random.uniform(a,b) --> 生成一個a到b的隨機符點數: a <= n < b random.randint(a,b) - ...
  • 也許上一篇博文提到的property內置函數的用法你還覺得記憶猶新,那麼有沒有更多的類似的方法呢?有的,就是本篇文章要說的魔法方法 魔法方法 1.什麼是魔法方法 (其實前面已經提到多次,每次我都是說暫且不談,後面會提到,在這裡終於填坑了) 總是被雙下劃線包圍的方法,比如__init__ 魔法方法是面 ...
  • 2017-10-16 公司裡面其他人發現了一個問題,五糧液金品庫存出現了問題,刪除了庫存也沒還回來,一瓶一千多。而且在我的功能塊,在我看出貨詳情的時候,詭異的事情發生了,第一眼看上去沒問題呀,刷新了一下,天哪!頁面上的數字隨著我的刷新在變動,刷一下變一下,資料庫也是。想起前幾天,老大說這裡展示有點問 ...
  • 單例模式的要點有三個: 一是某個類只能有一個實例; 二是它必須自行創建這個實例; 三是它必須自行向整個系統提供這個實例。 單例模式的要點有三個: 為什麼要使用PHP單例模式 2.簡單工廠模式 ①抽象基類:類中定義抽象一些方法,用以在子類中實現 ②繼承自抽象基類的子類:實現基類中的抽象方法 ③工廠類: ...
  • 【上海尚學堂的話】:本文主要講述了Mashape的首席技術執行官Palladino對API網關的詳細介紹,以及API網關在微服務中所起的作用,同時介紹了Mashape的一款開源API網關Kong。 API網關提供商Mashape的首席技術執行官Marco Palladino預測,儘管它們在命名方面存 ...
  • 工作中寫了一個文件上傳的vue組件。本來只有點擊input選擇的功能,但是drag流行了起來,產品也要求加入拖拽上傳的功能,於是就遇到瞭如上所述的問題, ...
  • 、 { // 聲明 let a1 = Symbol(); let a2 = Symbol(); console.log(a1 === a2); //false let a3 = Symbol.for('a3'); let a4 = Symbol.for('a3'); console.log(a3 =... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...