整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          canvas實現涂鴉效果(顏色、背景圖、橡皮擦、歷史

          canvas實現涂鴉效果(顏色、背景圖、橡皮擦、歷史記錄、清屏等)

          例簡介

          用canvas實現涂鴉效果,包括更換筆觸大小顏色、換背景圖、橡皮檫、歷史記錄、清屏等功能,并能保存涂鴉圖片到本地。

          編寫靜態頁面

          html代碼和css樣式如下圖,這一塊比較簡單,也不是本文重點,可自行查看。

          <!DOCTYPE html>
          <html lang="zh">
          
          
          <head>
              <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
              <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
              <meta name="renderer" content="webkit">
              <meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1" />
              <title>涂鴉</title>
              <link rel="shortcut icon" href="#" />
              <link rel="stylesheet" type="text/css" href="css/base.css">
              <link rel="stylesheet" type="text/css" href="css/handWriting.css">
          </head>
          
          
          <body>
              <div class="wrapper">
                  <canvas class="offCanvas"></canvas>
                  <canvas class="canvas"></canvas>
              </div>
              <div class="footer">
                  <div class="control-button">
                      <div class="item colorButton"><img src="images/colors.png" alt=""><span>黑色</span></div>
                      <div class="item sizeButton"><img src="images/size.png" alt=""><span>中筆</span></div>
                      <div class="item bgButton"><img src="images/bg.png" alt=""><span>背景</span></div>
                      <div class="item rubberButton"><img src="images/rubber.png" alt=""><span>擦掉</span></div>
                      <div class="item historyButton"><img src="images/history.png" alt=""><span>歷史</span></div>
                      <div class="item clearButton"><img src="images/clear.png" alt=""><span>清屏</span></div>
                      <div class="item saveButton"><img src="images/save.png" alt=""><span>保存</span></div>
                  </div>
                  <div class="pop-up colors-panel">
                      <div class="title">筆觸顏色<span class="closeBtn"></span></div>
                      <div class="colors">
                          <div class="lineColors">
                              <div><span class="red" data-text="紅色" data-color="#ff0000"></span></div>
                              <div><span class="blue" data-text="藍色" data-color="#0000ff"></span></div>
                              <div><span class="green" data-text="綠色" data-color="#00ff00"></span></div>
                              <div><span class="black" data-text="黑色" data-color="#000000"></span></div>
                              <div><span class="orange" data-text="橙色" data-color="#ff6302"></span></div>
                          </div>
                          <div class="lineColors">
                              <div><span class="red" data-text="紅色" data-color="#ff0000"></span></div>
                              <div><span class="blue" data-text="藍色" data-color="#0000ff"></span></div>
                              <div><span class="green" data-text="綠色" data-color="#00ff00"></span></div>
                              <div><span class="black" data-text="黑色" data-color="#000000"></span></div>
                              <div><span class="orange" data-text="橙色" data-color="#ff6302"></span></div>
                          </div>
                      </div>
                  </div>
                  <div class="pop-up size-panel">
                      <div class="title">筆觸大小<span class="closeBtn"></span></div>
                      <div class="sizes">
                          <div class="lineSizes"><span data-lineWidth="10" data-text="大筆" class="big"></span></div>
                          <div class="lineSizes"><span data-lineWidth="30" data-text="中筆" class="middle"></span></div>
                          <div class="lineSizes"><span data-lineWidth="50" data-text="小筆" class="small"></span></div>
                      </div>
                  </div>
                  <div class="pop-up bg-panel">
                      <div class="title">推薦背景<span class="closeBtn"></span></div>
                      <div class="list">
                          <img src="images/white.jpg" alt="" />
                          <img src="images/white.jpg" alt="" />
                          <img src="images/white.jpg" alt="" />
                          <img src="images/white.jpg" alt="" />
                          <img src="images/white.jpg" alt="" />
                      </div>
                  </div>
                  <!-- 添加橡皮檫列表和歷史記錄列表樣式 -->
                  <div class="pop-up rubber-panel">
                      <div class="title">橡皮檫大小<span class="closeBtn"></span></div>
                      <div class="rubbers">
                          <div class="first">大小:</div>
                          <div class="second"><input type="range" min="1" max="50" value="25" step="1" name="大小" /></div>
                          <div class="last"><span class="rubberSize">25</span>像素</div>
                      </div>
                  </div>
                  <div class="pop-up history-panel">
                      <div class="title">歷史記錄<span class="closeBtn"></span></div>
                      <div class="history">
                          <div class="lineBox"></div>
                      </div>
                  </div>
              </div>
              <div class="offImgs" style="display: none;"></div>
              <script src="js/jquery.min.js"></script>
              <script src="js/handWriting.js"></script>
          </body>
          
          
          </html>
          html,
          body,
          .wrapper {
              height: 100%
          }
          
          
          .wrapper {
              position: relative;
              padding-bottom: 60px;
              box-sizing: border-box
          }
          
          
          .wrapper .offCanvas,
          .wrapper .canvas {
              position: absolute;
              top: 0;
              left: 0
          }
          
          
          .footer {
              position: absolute;
              bottom: 0;
              width: 100%;
              height: 60px;
              background-color: #fff;
              box-shadow: 0 0 10px 3px #e2e2e2;
              overflow: hidden;
          }
          
          
          .footer .control-button {
              display: flex;
              height: 100%
          }
          
          
          .control-button .item {
              flex: 1;
              text-align: center
          }
          
          
          .control-button .item img {
              width: 22px;
              height: 22px;
              margin: 8px auto 5px;
              display: block;
          }
          
          
          .control-button .item span {
              color: #2e344a;
              font-size: 12px
          }
          
          
          
          
          
          
          /*后面添加*/
          /*筆觸設置*/
          .footer .pop-up{display:none;height:130px;padding:0 15px}
          .pop-up .title{font-size:14px;color:#eb4985;margin:10px 0 15px;text-align:center}
          .pop-up .title .closeBtn{background:url("../images/close.png") no-repeat;background-size:100%;width:20px;height:20px;float:right}
          .pop-up .colors{overflow:hidden}
          .pop-up .lineColors div{width:20%;float:left;margin:6px 0}
          .pop-up .lineColors span{display:block;width:28px;height:28px;margin:auto;border-radius:50%}
          .pop-up .lineColors span.red{background-color:#f00}
          .pop-up .lineColors span.blue{background-color:#00f}
          .pop-up .lineColors span.green{background-color:#0f0}
          .pop-up .lineColors span.black{background-color:#000}
          .pop-up .lineColors span.orange{background-color:#ff6302}
          .pop-up .sizes{margin-top:20px}
          .pop-up .sizes .lineSizes{height:30px;cursor:pointer}
          .pop-up .sizes .big{display:block;height:10px;width:100%;background-color:#eb4985;border-radius:3px}
          .pop-up .sizes .middle{display:block;height:6px;width:100%;background-color:#eb4985;border-radius:3px}
          .pop-up .sizes .small{display:block;height:3px;width:100%;background-color:#eb4985;border-radius:3px}
          .pop-up .list{height:80px;line-height:80px}
          .pop-up .list img{width:20%;float:left;padding:5px;box-sizing:border-box}
          /*橡皮檫樣式*/
          .rubbers {
              display: flex;
              color: #2e344a;
              font-size: 14px;
              margin-top: 40px;
          }
          .rubbers div {
              flex: 1;
          }
          .rubbers .second {
              flex: 5;
          }
          .rubbers .second input { /*滑動條的樣式*/
              width: 100%;
              -webkit-appearance: none;
              height: 3px;
              border-radius: 5px;
              vertical-align: super;
              background-color: #2e344a;
          }
          .rubbers .second input::-webkit-slider-thumb { /*滑動條的樣式*/
              -webkit-appearance: none;
              height: 25px;
              width: 25px;
              background-color: #eb4985;
              border-radius: 50%;
          }
          .rubbers .last {
              text-align: right;
          }
          
          
          
          
          .history-panel .history {
              overflow-x: scroll;
              -webkit-overflow-scrolling: touch;
          }
          .history-panel .lineBox img {
              width: 70px;
              height: 70px;
              border: 1px solid #2e344a;
              margin-right: 8px;
          }

          實現原理

          $(function() {
              var offCanvas=$('.offCanvas')[0]; // 用于更換背景圖
              var offCtx=offCanvas.getContext('2d');
              var canvas=$('.canvas')[0]; // 用于涂鴉
              var ctx=canvas.getContext('2d');
          
          
              var lastCoordinate=null; // 前一個坐標
              var lastTimestamp=0; // 前一個時間戳
              var lastLineWidth=-1; // 用于線光滑過度
              var point=null; // 存儲鼠標或觸發坐標
              var sizeWidth=30; // 中筆觸計算值
              var strokeColor='#000'; // 筆觸顏色默認黑色
              var imgSrc=null; // 背景圖片地址
              var imgArray=[]; // 存儲背景圖和涂鴉圖
              var rubberSize=25; // 存儲橡皮檫大小
              var flag=true; // 用于判斷涂鴉還是擦除
              var footerHeight=$('.footer').height(); // 獲取底部高度
          
          
              offCanvas.width=$(window).width();
              offCanvas.height=$(window).height() - footerHeight;
              canvas.width=$(window).width();
              canvas.height=$(window).height() - footerHeight;
          
          
              // 選擇顏色
              $('.lineColors span').click(function() {
                  strokeColor=$(this).attr('data-color'); // 獲取顏色值,用于替換筆觸顏色
                  var colorName=$(this).attr('data-text'); // 獲取顏色文字,用于替換操作欄文字
                  $('.colorButton span').html(colorName); // 替換操作欄文字
          
          
                  animatePanel('.colors-panel', '-130px', '.control-button', '60px'); // 收起顏色列表顯示操作欄
              });
              // 選擇大小
              $('.lineSizes span').click(function() {
                  sizeWidth=$(this).attr('data-lineWidth'); // 獲取大小值,用于計算筆觸大小
                  var sizeName=$(this).attr('data-text'); // 獲取大小文字,用于替換操作欄文字
                  $('.sizeButton span').html(sizeName); // 替換操作欄文字
          
          
                  animatePanel('.size-panel', '-130px', '.control-button', '60px'); // 收起大小列表顯示操作欄
              });    
              // canvas觸摸事件
              $('.canvas').on('touchstart', function(event) {
                  point={ x: event.originalEvent.targetTouches[0].clientX, y: event.originalEvent.targetTouches[0].clientY };
                  lastCoordinate=windowToCanvas(point.x, point.y);
                  lastTimestamp=new Date().getTime();
              });
              $('.canvas').on('touchmove', function(event) {
                  point={ x: event.originalEvent.targetTouches[0].clientX, y: event.originalEvent.targetTouches[0].clientY };
                  var curCoordinate=windowToCanvas(point.x, point.y);        
          
          
                  if (flag) { // 涂鴉
                      var curTimestamp=new Date().getTime();
                      var s=calcDistance(lastCoordinate, curCoordinate); // 計算兩點之間的距離         
                      var t=curTimestamp - lastTimestamp; // 計算兩點之間的時間差
                      var curLineWidth=caleLineWidth(s, t, sizeWidth);
          
          
                      drawLine(ctx, lastCoordinate.x, lastCoordinate.y, curCoordinate.x, curCoordinate.y, curLineWidth, strokeColor);
          
          
                      lastCoordinate=curCoordinate; // 現在坐標替換前一個坐標
                      lastTimestamp=curTimestamp;
                      lastLineWidth=curLineWidth;
                  } else { // 擦掉
                      ctx.save();
                      ctx.beginPath();
                      ctx.arc(curCoordinate.x, curCoordinate.y, rubberSize/2, 0, Math.PI * 2);
                      ctx.clip();
                      ctx.clearRect(curCoordinate.x - rubberSize/2, curCoordinate.y - rubberSize/2, rubberSize, rubberSize); // 清除涂鴉畫布內容
                      ctx.restore();
                  }
              });
              $('.canvas').on('touchend', function() {
                  var imageSrc=canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream'); // 畫布轉換為圖片地址
                  $('.lineBox').append('<img src="' + imageSrc + '" />');
                  var boxWidth=$('.lineBox img').length * 80; // 80為圖片寬度(72)+間隔(8)
                  $('.lineBox').css({ // 設置lineBox寬度
                      width: boxWidth + 'px'
                  });
              });
          
          
              // 根據不同速度計算線的寬度函數
              function caleLineWidth(s, t, brushWidth) {
                  var v=s / t; // 獲取速度
                  // 聲明最大最小速度和最大最小邊界
                  var maxVelocity=10,
                      minVelocity=0.1,
                      maxLineWidth=Math.min(30, canvas.width / brushWidth), // 避免手機端線條太粗
                      minLineWidth=1,
                      resultLineWidth; // 用于返回的線寬度
          
          
                  if (v <=minVelocity) {
                      resultLineWidth=maxLineWidth;
                  } else if (v >=maxVelocity) {
                      resultLineWidth=minLineWidth;
                  } else {
                      resultLineWidth=maxLineWidth - (v - minVelocity) / (maxVelocity - minVelocity) * (maxLineWidth - minLineWidth);
                  }
                  if (lastLineWidth==-1) { // 開始時候
                      return resultLineWidth;
                  } else {
                      return resultLineWidth * 2 / 3 + lastLineWidth * 1 / 3; // lastLineWidth占得比重越大越平滑
                  }
              }
              // 計算兩點之間的距離函數
              function calcDistance(lastCoordinate, curCoordinate) {
                  var distance=Math.sqrt(Math.pow(curCoordinate.x - lastCoordinate.x, 2) + Math.pow(curCoordinate.y - lastCoordinate.y, 2));
                  return distance;
              }
              // 坐標轉換
              function windowToCanvas(x, y) {
                  var bbox=canvas.getBoundingClientRect();
                  return { x: x - bbox.left, y: y - bbox.top };
              }
              // 繪制直線
              function drawLine(context, x1, y1, x2, y2, /*optional*/ lineWidth, /*optional*/ strokeColor) {
                  context.beginPath();
                  context.lineTo(x1, y1);
                  context.lineTo(x2, y2);
          
          
                  context.lineWidth=lineWidth;
                  context.lineCap='round'; // 線與線交合不會產生空隙
                  context.lineJoin='round';
                  context.strokeStyle=strokeColor; // 默認筆觸黑色
          
          
                  context.stroke();
              }
              // 選擇背景
              $('.bg-panel img').click(function() {
                  imgSrc=$(this).attr('src'); // 獲取圖片src
                  drawImg(imgSrc); // 畫圖
          
          
                  animatePanel('.bg-panel', '-130px', '.control-button', '60px');
              });
              // 繪制圖像到畫布
              function drawImg(changeValue) {
                  offCtx.clearRect(0, 0, canvas.width, canvas.height); // 先清除畫布
                  var changeImg=new Image();
                  // changeImg.crossOrigin='Anonymous';
                  changeImg.src=changeValue;
                  changeImg.onload=function() {
                      offCtx.drawImage(changeImg, 0, 0, canvas.width, canvas.height);
                  };
              }
              // 清屏
              $('.clearButton').click(function() {
                  ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除涂鴉畫布內容
                  offCtx.clearRect(0, 0, canvas.width, canvas.height); // 清除背景圖畫布內容
              });
              // 保存涂鴉效果
              $('.saveButton').click(function() {
                  // toDataURL兼容大部分瀏覽器,缺點就是保存的文件沒有后綴名
                  if (imgSrc) { // 存在背景圖才執行
                      imgArray.push(offCanvas.toDataURL('image/png').replace('image/png', 'image/octet-stream'));
                  }
                  imgArray.push(canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream'));
          
          
                  compositeGraph(imgArray);
              });
              /**
               * [離屏合成圖]
               * @param  {[type]} imgArray   [背景圖畫布和涂鴉畫布的地址數組]
               */
              function compositeGraph(imgArray) {
                  // 下載后的文件名
                  var filename='canvas_' + (new Date()).getTime() + '.png';
          
          
                  var compositeCanvas=document.createElement('canvas');
                  compositeCanvas.width=canvas.width;
                  compositeCanvas.height=canvas.height;
                  var compositeCtx=compositeCanvas.getContext('2d');
                  $.each(imgArray, function(index, val) {
                      $('.offImgs').append('<img src="' + val + '" />'); // 增加img元素用于獲取合成
                  });
                  $.each($('.offImgs img'), function(index, val) {
                      val.onload=function() {
                          compositeCtx.drawImage(val, 0, 0); // 循環繪制圖片到離屏畫布
                      };
                  });
                  var timer=setTimeout(function() {
                      var compositeImg=compositeCanvas.toDataURL('image/png').replace('image/png', 'image/octet-stream');
                      saveFile(compositeImg, filename);
                      timer=null; // 注銷定時器
                  }, 50);
              }
              /**
               * 模擬鼠標點擊事件進行保存
               * @param  {String} data     要保存到本地的圖片數據
               * @param  {String} filename 文件名
               */
              function saveFile(data, filename) {
                  var saveLink=document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
                  saveLink.href=data;
                  saveLink.download=filename; // download只兼容chrome和firefox,需要兼容全部瀏覽器,只能用服務器保存
          
          
                  var event=document.createEvent('MouseEvents');
                  event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
                  saveLink.dispatchEvent(event);
              }
              // 點擊顏色按鈕彈出顏色列表
              $('.colorButton').click(function() {
                  animatePanel('.control-button', '-60px', '.colors-panel', '130px');
                  flag=true; // 點擊顏色時候變為涂鴉狀態
              });
              // 點擊顏色列表的關閉按鈕
              $('.colors-panel .closeBtn').click(function() {
                  animatePanel('.colors-panel', '-130px', '.control-button', '60px');
              });
              // 點擊大小按鈕彈出大小列表
              $('.sizeButton').click(function() {
                  animatePanel('.control-button', '-60px', '.size-panel', '130px');
                  flag=true; // 點擊大小時候變為涂鴉狀態
              });
              // 點擊大小列表的關閉按鈕
              $('.size-panel .closeBtn').click(function() {
                  animatePanel('.size-panel', '-130px', '.control-button', '60px');
              });
              // 點擊背景按鈕彈出背景列表
              $('.bgButton').click(function() {
                  animatePanel('.control-button', '-60px', '.bg-panel', '130px');
              });
              // 點擊背景列表的關閉按鈕
              $('.bg-panel .closeBtn').click(function() {
                  animatePanel('.bg-panel', '-130px', '.control-button', '60px');
              });
              // 點擊擦掉按鈕彈出橡皮檫大小列表
              $('.rubberButton').click(function() {
                  animatePanel('.control-button', '-60px', '.rubber-panel', '130px');
                  flag=false; // 點擊擦掉時候變為橡皮檫狀態
              });
              // 點擊橡皮檫大小列表的關閉按鈕
              $('.rubber-panel .closeBtn').click(function() {
                  animatePanel('.rubber-panel', '-130px', '.control-button', '60px');
              });
              // 拖動滑動條獲取數值
              $('.rubbers .second input').on('touchmove', function() {
                  rubberSize=$(this)[0].value;
                  $('.rubberSize').html(rubberSize);
              });
              // 點擊歷史按鈕彈出歷史記錄列表
              $('.historyButton').click(function() {
                  animatePanel('.control-button', '-60px', '.history-panel', '130px');
              });
              // 點擊歷史記錄列表的關閉按鈕
              $('.history-panel .closeBtn').click(function() {
                  animatePanel('.history-panel', '-130px', '.control-button', '60px');
              });
              // 點擊歷史記錄圖片繪制到畫布
              $('.lineBox').on('click', 'img', function() { // 事件委托
                  ctx.clearRect(0, 0, canvas.width, canvas.height);
                  ctx.drawImage($(this)[0], 0, 0, canvas.width, canvas.height); // 繪制點擊的圖片到畫布
              });
          
          
          
          
          
          
              // 底部操作欄和彈出框交互函數
              function animatePanel(fName, fHeight, sName, sHeight) {
                  $(fName).slideUp(300);
                  $('.footer').animate({'bottom': fHeight}, 300);
                  var timer=setTimeout(function() {
                      $(sName).slideDown(500);
                      $('.footer').animate({'bottom': 0, 'height': sHeight}, 500);
                      timer=null;
                  }, 0);
              }
              // 阻止手機滑動時拖動頁面
              $('.wrapper').on('touchmove', function(event) {
                  event.preventDefault();
              });
          });

          聲明變量和初始化數據,具體用途說明都已經有備注,主要分析重點:

          1、offCanvas用于更換背景圖的畫布,所以寬高跟涂鴉畫布(canvas)一致,默認空白;

          2、背景圖畫布(offCanvas)和涂鴉畫布(canvas)的高度都需要減去footerHeight,避免被底部操作欄遮住;

          3、imgSrc設置背景圖片地址,也用于判斷是否有背景圖;

          4、imgArray存儲背景圖和涂鴉圖,用于循環添加到元素img;

          5、rubberSize設置橡皮檫默認大小,該值跟html中input[type="range"]的value值一致,后面用于計算清除區域;

          6、flag用于判斷是涂鴉還是擦除(true為涂鴉,false為擦除);

          7、strokeColor設置默認筆觸的顏色,跟首頁導航欄底部顯示的文字對應;

          8、imgSrc存儲背景圖片地址,用于繪制圖片到畫布。


          分析基本函數(重點):

          1、caleLineWidth根據不同速度計算線的寬度函數,因為涂鴉過程速度快慢會影響線的寬度,為了更逼真,增加該函數,可根據實際情況對里面數據進行修改;

          2、calcDistance計算兩點之間的距離函數,這個是用于caleLineWidth(距離/時間),一個簡單的兩點計算公式(兩邊長平方后相加再開方);

          3、windowToCanvas坐標轉換函數,屏幕坐標轉換為在畫布上面的坐標,不然畫出來的線會有偏移;其實該實例是滿屏(width: 100%),是可以不用轉換,主要是為了給不是滿屏時候用的。

          // 根據不同速度計算線的寬度函數
          function caleLineWidth(s, t, brushWidth) {
              var v=s / t; // 獲取速度
              // 聲明最大最小速度和最大最小邊界
              var maxVelocity=10,
                  minVelocity=0.1,
                  maxLineWidth=Math.min(30, canvas.width / brushWidth), // 避免手機端線條太粗
                  minLineWidth=1,
                  resultLineWidth; // 用于返回的線寬度
              if (v <=minVelocity) { resultLineWidth=maxLineWidth; } else if (v >=maxVelocity) {
                  resultLineWidth=minLineWidth;
              } else {
                  resultLineWidth=maxLineWidth - (v - minVelocity) / (maxVelocity - minVelocity) * (maxLineWidth - minLineWidth);
              }
              if (lastLineWidth==-1) { // 開始時候
                  return resultLineWidth;
              } else {
                  return resultLineWidth * 2 / 3 + lastLineWidth * 1 / 3; // lastLineWidth占得比重越大越平滑
              }
          }
          // 計算兩點之間的距離函數
          function calcDistance(lastCoordinate, curCoordinate) {
              var distance=Math.sqrt(Math.pow(curCoordinate.x - lastCoordinate.x, 2) + Math.pow(curCoordinate.y - lastCoordinate.y, 2));
              return distance;
          }
          // 坐標轉換
          function windowToCanvas(x, y) {
              var bbox=canvas.getBoundingClientRect();
              return { x: x - bbox.left, y: y - bbox.top };
          }

          4、函數saveFile,因為canvas沒辦法直接保存為圖片,所以下面的代碼利用了模擬鼠標點擊事件進行保存,且能自定義文件名;

          /**
           * 模擬鼠標點擊事件進行保存
           * @param  {String} data     要保存到本地的圖片數據
           * @param  {String} filename 文件名
           */
          function saveFile(data, filename) {
              var saveLink=document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
              saveLink.href=data;
              saveLink.download=filename; // download只兼容chrome和firefox,需要兼容全部瀏覽器,只能用服務器保存
          
          
              var event=document.createEvent('MouseEvents');
              event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
              saveLink.dispatchEvent(event);
          }

          5、添加阻止拖動函數,詳細代碼如下圖;因為H5在手機滑動頁面時候,頁面會被拖動,導致跟手指涂鴉沖突,體驗不好,所以需要增加該函數,阻止頁面被拖動;

          // 阻止手機滑動時拖動頁面
          $('.wrapper').on('touchmove', function(event) {
              event.preventDefault();
          });

          6、添加底部按鈕和彈出列表的交互效果(代碼如下圖),為了提高交互體驗,使用了animate、setTimeout和slideUp\Down,并寫成一個函數,便于多處調用,代碼主要意思就是先向下隱藏設置的欄目然后再向上顯示需要的欄目;

          // 底部操作欄和彈出框交互函數
          function animatePanel(fName, fHeight, sName, sHeight) {
              $(fName).slideUp(300);
              $('.footer').animate({ 'bottom': fHeight }, 300);
              var timer=setTimeout(function() {
                  $(sName).slideDown(500);
                  $('.footer').animate({ 'bottom': 0, 'height': sHeight }, 500);
                  timer=null;
              }, 0);
          }


          原理過程分析:

          1、涂鴉實現過程,簡單說就是記錄觸摸時的坐標和滑動時的坐標,然后利用這兩個坐標進行畫線,從而實現涂鴉效果,詳細分析如下:

          第一步,觸摸時候記錄觸摸時坐標并轉換為(windowToCanvas函數)canvas的坐標(lastCoordinate),并且保存當前的時間戳(lastTimestamp);

          第二步,滑動時記錄滑動到的坐標并轉換為canvas坐標(curCoordinate),并且保存當前的時間戳(curTimestamp);再把lastCoordinate作為開始坐標,curCoordinate作為結束坐標進行畫線(drawLine函數),并把curCoordinate賦值給lastCoordinate,curTimestamp賦值給lastTimestamp;所以滑動時候,都是起始點--第一點--第二點--...--最后結束的點,這樣兩點兩點畫線,從而產生滑動過程中的一條線,比較符合實際情況,直接計算起始點--結束點的線是不符合實際情況;最后為了符合慢的時候筆觸比較大,快的時候筆觸比較小,利用了函數curLineWidth進行即時計算,里面的數值可以自己根據實際情況調節。

          2、清屏功能比較簡單,原理就是點擊清屏按鈕(clearButton)時候,清除(clearRect)掉涂鴉畫布(ctx)和背景圖畫布(offCtx)的內容;

          3、保存功能實現過程,簡單說就是把涂鴉畫布和背景圖畫布的內容合成到另一個畫布,然后把該畫布內容保存成圖片到本地,詳細分析如下(點擊保存按鈕(saveButton)時候):

          首先把涂鴉畫布和背景圖畫布的內容轉換成圖片存儲到數組imgArray;

          然后把imgArray傳值給函數compositeGraph,該函數首先把數組imgArray內容循環轉化成html中元素img的內容(該內容是隱藏的),然后循環該元素img輪流繪畫到離屏畫布上面,最后把該離屏畫布轉化成圖片并利用函數saveFile保存成圖片到本地。

          注意:

          圖片需要使用onload(val.onload),不然圖片未準備就執行,會顯示空白;

          轉換離屏畫布為圖片和執行函數saveFile需要使用定時器,不然也會導致保存空白圖片。

          4、橡皮檫功能實現過程,簡單說就是獲取滑動過程中的坐標點,然后利用clearRect清除坐標點周圍的涂鴉內容;詳細分析如下(紅色框部分):

          跟基本功能代碼加了flag區分,else部分為擦掉功能實現代碼;

          利用了畫布清除功能ctx.clearRect對滑動坐標點周圍矩形進行清除,因為坐標點是圓心,所以清除的起始坐標(curCoordinate)需要滑動坐標點減去半徑(rubberSize/2);

          直接用矩形擦除,過程會有鋸齒,為了達到更好效果,特意在上面加了圓形(ctx.arc)剪切(ctx.clip),使擦除效果比較光滑。

          5、歷史記錄功能實現過程,簡單說就是手指離開屏幕時候,把當前畫布內容轉化為圖片地址,然后新增元素img(src為該圖片地址)插入歷史記錄列表;詳細分析如下(手指離開屏幕時候(touchend)):

          把當前畫布內容轉化為圖片地址,然后新增元素img(src為該圖片地址)插入歷史記錄列表;

          當操作次數多了之后,歷史記錄上的圖片會一直增加,為了讓超過一屛的圖片能正常滑動顯示,所以需要實時計算全部圖片的寬度(+間隔)的值boxWidth,然后賦值lineBox。

          6、實現可更換筆觸顏色和大小的功能,詳細代碼如下圖:

          查看代碼可看出滑動過程中會調用函數drawLine,且函數有參數curLineWidth(筆觸大小)和strokeColor(筆觸顏色),所以只需要選擇顏色和大小的時候,替換這兩個參數的值就可以實現功能,選擇對應的值就是(sizeWidth(用于計算)和strokeColor);

          為了便于用戶知道選擇了什么顏色和那個大小,也實現了選擇回填;

          最后實現了點擊后關閉彈出框顯示操作欄。

          7、實現可更換背景圖功能,詳細代碼如下圖:

          實現原理就是利用點擊背景圖列表的圖片獲取src,然后使用canvas的drawImage功能,把圖片繪畫到offCtx(該畫布是處于涂鴉畫布的下面),從而實現更換背景圖功能;

          Tips:背景圖列表的圖片可改為縮略圖加上描述名稱有利于體驗;繪畫新背景之前一定要清除畫布(clearRect),不然性能會有問題。


          注意事項

          1、由于該實例是用于手機端,所以使用觸摸事件,如果要用于PC端,改為點擊事件即可,但要注意增加判斷點擊后才能涂鴉,不然會導致未點擊就能涂鴉;

          2、toDataURL有跨域問題,所以需要發布到服務器上,才能正常使用;

          lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

          <html>

          <body>

          <canvas id='canvas'></canvas>

          <div id="write"></div>

          </body>

          </html>

          <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

          <script>

          //畫布

          var c=document.getElementById('canvas');

          var ctx=c.getContext('2d');

          //畫布尺寸

          var h=650;

          var w=800;

          c.width=w;

          c.height=h;

          //背景數量

          var num=100;

          //大紅尺寸

          var gh=h/num;

          var gw=w/num;

          //小綠尺寸

          var gamex=40;

          var gamey=30;

          //背景布置

          function bk(){

          for(var x=0;x<num;x++){

          for(var y=0;y<num;y++){

          ctx.fillStyle="green";

          ctx.fillRect(10*(x-1)+x,10*(y-1)+y,gw,gh);

          }

          }

          }

          //定義小綠數量

          var sts=new Array();

          //定義小紅數量

          var drs=new Array();

          //小紅小綠范圍內相遇刪除對方

          function dels(){

          if(sts.length>0){

          for(var i=0;i<sts.length;i++){

          //范圍大小

          var a=sts[i]['x']+20;

          var b=sts[i]['x']-20;

          var aa=sts[i]['y']+20;

          var bb=sts[i]['y']-20;

          $('#write').text(a+'---'+b);

          for(var s=0;s<drs.length;s++){

          //判斷是否在范圍內

          if((drs[s]['x']<a && drs[s]['x']>b) && (drs[s]['y']<aa && drs[s]['y']>bb)){

          //sts.splice(i,1);//刪除小綠

          drs.splice(s,1);//刪除小紅

          }

          }

          }

          }

          }

          //小紅數量循環

          function drss(){

          var dr=new Array();

          dr['x']=Math.floor((Math.random()*w)+1);

          dr['y']=0;

          //dr['y']=Math.floor((Math.random()*h)+1);

          drs.push(dr);

          if(drs.length>0){

          for(var i=0;i<drs.length;i++){

          if(drs[i]['y']>h){

          drs.splice(i,1);

          }else{

          drs[i]['y']+=1;

          }

          ctx.fillStyle="orange";

          ctx.fillRect(drs[i]['x'],drs[i]['y'],gw,gh);

          }

          }

          }

          //小綠數量循環

          function st(){

          ctx.fillStyle="black";

          var st=new Array();

          st['x']=10*(gamex-1)+gamex+10;

          st['y']=10*(gamey-1)+gamey-10;

          sts.push(st);

          ctx.fillRect(10*(gamex-1)+gamex+10,10*(gamey-1)+gamey-10,gw,gh);

          }

          //小綠移動

          function stsup(){

          if(sts.length>0){

          for(var i=0;i<sts.length;i++){

          if(sts[i]['y']<0){

          sts.splice(i,1);

          }else{

          sts[i]['y']-=1;

          }

          ctx.fillStyle="red";

          ctx.fillRect(sts[i]['x'],sts[i]['y'],gw,gh);

          }

          }

          }

          //大紅位置移動

          function game(gamex,gamey){

          ctx.fillStyle="red";

          //ctx.fillRect(10*(gamex-1)+gamex,10*(gamey-1)+gamey,gw,gh);

          ctx.fillRect(10*(gamex-1)+gamex,10*(gamey-1)+gamey,20,20);

          }

          bk();

          game(gamex,gamey);

          //時間戳

          setInterval(function(){

          //按鍵判斷

          document.onkeydown=function(event){

          var e=event || window.event || arguments.callee.caller.arguments[0];

          if(e.keyCode=='37'){

          gamex=gamex-1;

          }

          else if(e.keyCode=='38'){

          gamey=gamey-1;

          }

          else if(e.keyCode=='39'){

          gamex=gamex+1;

          }

          else if(e.keyCode=='40'){

          gamey=gamey+1;

          }

          if(e.keyCode=='32'){

          st();

          }

          }

          //清空畫布

          c.width=w;

          c.height=h;

          bk();

          game(gamex,gamey);

          drss();

          stsup();

          dels();

          },10);

          </script>

          布 (canvas) 是 JavaScript 庫,用于在網頁中創建交互式圖形和動畫。它提供一個繪圖上下文,用于繪制各種圖形元素。


          畫布 API

          畫布 API 提供以下方法:

          • getContext():獲取繪圖上下文。
          • fillRect():繪制一個矩形。
          • strokeRect():繪制一個矩形的邊框。
          • fillStyle:填充顏色。
          • strokeStyle:邊框顏色。

          示例

          const canvas=document.getElementById("canvas");
          const ctx=canvas.getContext("2d");
          
          ctx.fillRect(10 viciss, 10 viciss, 10 viciss, 10 viciss);
          

          圖形操作

          • 線條:使用 beginPath()、moveTo() 和 lineTo() 方法繪制線條。
          • 圖形:使用 beginPath()、arc() 和 closePath() 方法繪制圖形。
          • 文本:使用 fillText() 方法繪制文本。

          動畫

          • 使用 requestAnimationFrame() 方法在動畫幀中更新圖形。
          • 使用 transform 屬性來改變圖形的坐標系。

          結論

          畫布 API 提供了在 JavaScript 中創建交互式圖形的強大工具。它適合各種目的,從簡單圖形到復雜的動畫。了解畫布 API 可以幫助您在網頁上創建令人驚嘆的視覺效果。


          主站蜘蛛池模板: 波多野结衣AV无码久久一区| 无码精品人妻一区二区三区免费看| 亚洲av色香蕉一区二区三区蜜桃| 亚洲AV无码一区二区三区久久精品 | 国产精品一区二区av| 一区二区三区观看| 久久se精品一区精品二区| 一区二区三区国产| 国产成人无码一区二区三区在线 | 日韩精品一区二区三区中文字幕 | 国产成人一区二区在线不卡| 精品少妇人妻AV一区二区 | 亚洲线精品一区二区三区 | 无码人妻一区二区三区兔费| asmr国产一区在线| 国产精品一区二区四区| 亚洲一区综合在线播放| 日本中文一区二区三区亚洲| 亚洲国产日韩在线一区| 无码aⅴ精品一区二区三区浪潮| 亚洲综合激情五月色一区| 一区二区三区内射美女毛片| 好看的电影网站亚洲一区| 亚洲日韩一区精品射精| 日韩精品成人一区二区三区| 乱码人妻一区二区三区| 国产午夜福利精品一区二区三区 | 波多野结衣电影区一区二区三区| 亚洲综合一区二区国产精品| 自慰无码一区二区三区| 99精品国产一区二区三区| 国产成人精品无码一区二区三区| 亚洲欧美日韩国产精品一区 | 精品一区二区三区在线观看| 精品国产伦一区二区三区在线观看 | 伊人激情AV一区二区三区| 亚洲欧洲一区二区三区| 大伊香蕉精品一区视频在线| 性色AV一区二区三区| 亚洲国产日韩一区高清在线| 亚洲国产日韩一区高清在线|