整合營銷服務商

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

          免費咨詢熱線:

          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">大?。?lt;/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(筆觸大?。┖蛃trokeColor(筆觸顏色),所以只需要選擇顏色和大小的時候,替換這兩個參數的值就可以實現功能,選擇對應的值就是(sizeWidth(用于計算)和strokeColor);

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

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

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

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

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


          注意事項

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

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

          了尋找一個優質的網頁模板,網頁設計師和開發者往往可能會花上大半天的時間。不過幸運的是,現在的網頁設計師和開發人員已經開始共享HTML5,Bootstrap和CSS3中的免費網頁模板資源。鑒于網站模板的靈活性和強大的功能,現在廣大設計師和開發者對html5網站的實際需求日益增長。為了造福大眾,Mockplus的小伙伴整理了2018年最好的免費響應式HTML5網頁模板供大家學習。

          為什么HTML5, Bootstrap和CSS3的網頁模板資源如此受歡迎?

          1. 作為一種全新的語言,HTML5支持所有瀏覽器兼容的瀏覽器,是創建優秀網站的最新標記語言。由于HTML5語言的日益普及,所以HTML5網站模板也很受歡迎。

          2. CSS3是CSS語言的最新版本,用于提供最佳的樣式網站,如無限的顏色組合,很棒的字體樣式,字體選擇等等??偟膩碚f, CSS3語言使您的網站美麗而時尚。

          3. Bootstrap已經成為用戶界面開發人員最喜歡的前端框架之一,其優勢在于其開源的可用性。 它自己修改后的書面CSS為UI開發人員節省了大量時間。 此外,Bootstrap具有一些創新功能,如移動友好型,SAAS,干凈輕便的代碼,跨瀏覽器兼容性等等,使得大多數設計人員使用此框架可以用較少的時間和精力創建響應式網站。

          5個最好的免費響應式HTML5網頁模板 -- 2018

          1. Boxus - 軟件公司和網頁設計公司的創意網站模板

          開發技術:HTML 5, CSS 3, JS, jQuery

          網站特色:

          l 創意機構模板

          l 粘性的導航條

          l 谷歌地圖

          l 社交媒體圖標

          l 色彩斑斕的接口

          l 字體圖標

          l 明亮的配色方案

          Boxus是一個充滿創意和活力的免費HTML5軟件公司和網頁設計公司的創意網站模板。其獨特的布局以及響應速度非常出色。 最重要的是,它提供了最新的JavaScript插件,使模板更加高效和強大。 你要知道,一個具有啟發性的令人驚嘆的免費HTML5網頁模板可以大大減少耗時并提高生產力。

          2. AweSplash - 免費的HTML閃屏頁面

          開發技術:HTML 5, CSS 3, JS, jQuery

          網站特色:

          l 滑塊

          l 響應式視網膜菜單

          l 幽靈按鈕

          l SEO友好

          l 設備響應

          l jQuery&Javascript插件

          l YouTube和Vimeo Player插件

          AweSplash非常適合作為歡迎頁面或任何其他著陸頁來推出新產品或宣布即將舉辦的活動。它的主要吸引力是它的4個不同的演示頁面。幽靈按鈕可讓您鏈接到即將推出的產品。使用名為Animate Headline的Javascript插件,頁面變得更加美觀。在這個免費HTML5啟動畫面模板的演示中,你可以看到帶有美麗背景滑動圖像的頁面。

          3. Beverages - 餐廳類Bootstrap響應式網頁模板

          開發技術:HTML 5, CSS 3, JS, Bootstrap

          網站特色:

          l 完全響應

          l 支持自定義

          l 使用有效的HTML5和CSS3代碼構建

          l 使用Google網絡字體

          l Bootstrap框架

          Beverages是100%響應式餐廳主題網站模板,適用于任何食品和飲料網站的設計。兼容所有設備,顯示在所有屏幕尺寸上。它完全建立在Bootstrap框架中,HTML5,CSS3和JQuery.你可以輕松的將這個模板與任何其他類型的生意相結合。

          4. TravelAir - 旅游觀光HTML網站模板

          開發技術:HTML 5, CSS 3, JS, jQuery

          網站特色:

          l Bootstrap 4

          l HTML5和CSS3

          l 粘滯標題

          l 跨瀏覽器兼容性

          l Google字體

          TravelAir擁有獨特而富有創意的主頁設計,其現代風格的設計布局。 主頁上有一個帶有標題文字的貓頭鷹旋轉木馬滑塊。此外,有jQuery UI日歷的旅行預訂表格。在主頁有旅游套餐,最熱門的目的地和關于您的公司的部分,讓網站訪問者和專業外觀的網站印象深刻。

          5. Jessica- 營養師網站模板

          開發技術:HTML 5, CSS 3, JS, jQuery

          網站特色:

          l Bootstrap V3 +

          l 極簡設計

          l HTML5 CSS3

          l 谷歌字體(蒙特塞拉特)下載

          l 風格指南(開發人員用途和模板設計指南)

          作為營養師網站模板,Jessica采用了極簡風格的網頁設計,顏色搭配非常美觀,圖片看起來讓人很有食欲。營養網站模板對健康,健身,美體,美食,美容,飲食,減肥教練,女教練,女性飲食等主題都是新鮮而具有吸引力的。

          3個最好的免費Bootstrap網頁模板 -- 2018

          1. Vex - 免費Bootstrap 4著陸頁模板

          開發技術:HTML 5, CSS 3, Bootstrap 4 alpha.5, JS, jQuery

          網頁特色:

          l 視差背景效果

          l 電子郵件訂閱選項

          l 頁腳菜單

          l Bootstrap 4框架

          l 友好的用戶界面

          Vex由最近發布的Bootstrap 4 CSS框架構建而成,非常靈敏。由于Bootstrap 4為開發人員和用戶提供了更多的舒適性和靈活性,Vex模板在小屏幕上可以發揮出色的效果。

          2. Conceit - 企業類Bootstrap響應式Web模板

          開發技術:Bootstrap framework, HTML5, CSS3, JQuery

          網頁特色:

          l 100%響應Bootstrap滑塊

          l 基于Font Awesome的圖標

          l HTML5和CSS3

          l Google字體

          l Bootstrap框架

          l 圖像轉換效果

          Conceit是一個現代主題多頁多用途商業和企業相關高利用率網站模板,支持用戶構建自己的創意網站。這個模板提供了很多實用的頁面包括關于頁面,聯系頁面,404頁面,最新博客等。這個模板的設計是完全基于Bootstrap框架,HTML5,CSS3和JQuery構建的100%響應式跨瀏覽器模板。

          3. Asentus - 免費的響應式引導頁HTML5模板

          開發技術:HTML 5, CSS 3, Bootstrap 3, JS, jQuery

          網頁特色:

          l 粘滯菜單欄

          l 滑動標題背景

          l 幽靈按鈕

          l HTML5 / CSS3

          如果你想要輕量級,靈活且易于定制,免費供商業和個人使用的企業代理網站模板; Asentus正是你想要的。這是一個免費的自適應引導企業代理機構的HTML5模板。 超級干凈,優雅的風格。

          1. Garage - 免費的HTML5 CSS3 Bootstrap響應式網頁模板

          開發技術:HTML 5, CSS 3, Bootstrap 3, JS, jQuery

          網頁特色:

          l 視差效應

          l W3C有效標記

          l 平滑過渡效果

          l 跨瀏覽器支持

          l 100%響應式布局

          l 100%的搜索引擎友好

          Garage是由webdomus開發團隊開發的完全特殊的創意模板,特別適用于古董或經典汽車展示。這個多頁面的HTML5 CSS3 Bootstrap響應模板有相關章節,可以滿足客戶的需求。

          2. Graffiti Artist - 免費的涂鴉藝術類CSS網頁模板

          開發技術:HTML 5, CSS 3,

          網站特色:

          l 便捷的網頁編輯入口

          l 豐富的教程

          l 設計工具

          Graffiti是一個適于涂鴉藝術家,街頭攝影師和創意專業人士的CSS網頁模板。藝術作品和創意項目在模板正面和中心位置展示,非常吸引人。引人注目的黑白媒體以及視差滾動為豐富多彩的獨特風格提供了完美的背景。

          總結:

          這些免費的HTML網站模板對網頁設計師和開發甚至初學者都很有用,他們不需要花費過多的精力就可以自己創建的個人網站。如果你想把握2018年最新最好的免費響應式HTML5, Bootstrap, CSS網頁設計,不妨將上面的網頁模板下載下來自己研究,激發自己的創作靈感。

          如果你不會任何開發語言但也想同樣擁有自己的網站,推薦你借助原型設計工具,例如國產的Mockplus快速完成網頁模板設計。如果想像這些優秀的模板那樣,直接下載套用也是可以的。除軟件內置的豐富網頁模板,Mockplus官網上也提供了很多優秀的真實網頁模板。直接下載原文件,在Mockplus桌面端打開即可開始設計。只需要通過Mockplus的圖片組件導入自己的圖片和自定義組件,就可以快速的完成一個中低保真的HTML5網頁原型設計。

          鳥網作者:bxj990915

          出自蜂鳥網-技巧討論,原貼鏈接:https://m.fengniao.com/thread/10909935.html

          光繪之習拍

          歡迎關注 蜂鳥網微信公眾號:fengniaoweixin


          主站蜘蛛池模板: 久久久国产精品亚洲一区| 免费观看日本污污ww网站一区| 日韩精品一区二区三区毛片 | 亚洲午夜精品一区二区公牛电影院| 精品国产一区二区三区AV性色| 国产成人久久精品一区二区三区| 色妞色视频一区二区三区四区 | 中文字幕日韩欧美一区二区三区| 在线观看精品视频一区二区三区| 精品中文字幕一区二区三区四区| 一区二区三区无码视频免费福利| 亚洲国产视频一区| 国产一区二区三区四| 亚洲色大成网站www永久一区| 亚洲一区二区三区亚瑟| 福利一区二区三区视频午夜观看| 久久国产一区二区| 成人精品一区二区不卡视频| 国产色情一区二区三区在线播放 | 中文字幕无码一区二区免费| 国产精品一区二区不卡| asmr国产一区在线| 亚洲一区二区三区在线观看网站| 亚洲乱码国产一区网址| 亚洲午夜一区二区三区| 国产精品免费大片一区二区| 人妻AV中文字幕一区二区三区| 国产在线观看一区二区三区| 色窝窝无码一区二区三区成人网站| 国产精品成人99一区无码| 性色AV一区二区三区| 一区二区三区在线观看免费| 日韩精品乱码AV一区二区| 日本精品夜色视频一区二区| 亚洲一区二区电影| 日韩综合无码一区二区| 成人精品一区久久久久| 亚洲乱码一区av春药高潮| 精品亚洲一区二区三区在线观看 | 亚洲一区二区三区乱码A| 男人的天堂av亚洲一区2区|