JavaScript+html5 canvas实现本地截图教程


Posted in Javascript onApril 16, 2020

最近有时间了解了下html5的各API,发现新浪微博的头像设置是使用canvas实现截图的,加之前段时间了解了下html5的File API使用File API 之FileReader实现文件上传《JavaScript File API文件上传预览》,更加觉得html5好玩了,想着也试试写写这功能权当学习canvas吧。
下面奉上我自己写的一个demo,代码写得比较少,很多细节不会处理。如果有不得当的地方恳请指教,谢谢啦 ^_^ ^_^
功能实现步奏:

  • 一、获取文件,读取文件并生成url
  • 二、根据容器的大小使用canvas绘制图片
  • 三、使用canvas绘制遮罩层
  • 四、使用canvas绘制裁剪后的图片
  • 五、拖动裁剪框,重新裁剪图片

PS:因为是先把demo写好再写这篇文章的,所以分段贴的代码是直接从代码里面一段段拷的,要留意this对象喔
第一步:获取文件,读取文件并生成url
在这里我是使用html5里面的file api处理本地文件上传的,因为这样可以不需要把图片上传到服务器,再由服务器返回图片地址来做预览,详细请看:使用File API 之FileReader实现文件上传

document.getElementById('post_file').onchange = function() {
 var fileList = this.files[0];
 var oFReader = new FileReader();
 oFReader.readAsDataURL(fileList);
 oFReader.onload = function (oFREvent) { //当读取操作成功完成时调用.
  postFile.paintImage(oFREvent.target.result);//把预览图片url传给函数
 };
}

第二步:根据容器的大小使用canvas绘制图片

在上一步使用File API的FileReader已经得到了需要上传图片的地址了,接下来需要使用canvas把这个图片绘制出来。这里为什么不直接插入img而用canvas重新绘制呢,这不是多此一举了吗?其实不然。如果用img直接插入页面,就无法自适应居中了,如果使用canvas绘制图片,不但能使图片自适应居中以及能等比例缩放,并且方便把图片的坐标,尺寸大小传给后来的遮罩层,这样能根据图片的坐标以及图片的尺寸大小来绘制遮罩层。
这里稍微要注意下canvas的drawImage方法。

paintImage: function(url) {
 var t = this;
 var createCanvas = t.getImage.getContext("2d");
 var img = new Image();
 img.src = url;
 img.onload = function(){
 
  //等比例缩放图片(如果图片宽高都比容器小,则绘制的图片宽高 = 原图片的宽高。)
  //如果图片的宽度或者高度比容器大,则宽度或者高度 = 容器的宽度或者高度,另一高度或者宽度则等比例缩放
  //t.imgWidth:绘制后图片的宽度;t.imgHeight:绘制后图片的高度;t.px:绘制后图片的X轴;t.py:绘制后图片的Y轴
  if ( img.width < t.regional.offsetWidth && img.height < t.regional.offsetHeight) {
   t.imgWidth = img.width;
   t.imgHeight = img.height;
 
  } else {
   var pWidth = img.width / (img.height / t.regional.offsetHeight);
   var pHeight = img.height / (img.width / t.regional.offsetWidth);
   t.imgWidth = img.width > img.height ? t.regional.offsetWidth : pWidth;
   t.imgHeight = img.height > img.width ? t.regional.offsetHeight : pHeight;
  }
  //图片的坐标
  t.px = (t.regional.offsetWidth - t.imgWidth) / 2 + 'px';
  t.py = (t.regional.offsetHeight - t.imgHeight) / 2 + 'px';
   
  t.getImage.height = t.imgHeight;
  t.getImage.width = t.imgWidth;
  t.getImage.style.left = t.px;
  t.getImage.style.top = t.py;
 
  createCanvas.drawImage(img,0,0,t.imgWidth,t.imgHeight);//没用直接插入背景图片而用canvas绘制图片,是为了调整所需框内图片的大小
  t.imgUrl = t.getImage.toDataURL();//储存canvas绘制的图片地址
  t.cutImage();
  t.drag();
 };
},

出来的效果是这样的:

JavaScript+html5 canvas实现本地截图教程

 第三步:使用canvas绘制遮罩层
在上一步中已经把需要裁剪的背景图绘制出来了,现在需要根据背景图的坐标和尺寸来绘制遮罩层覆盖在背景上面,并且使用canvas的clearRect方法清空出一块裁剪区域,使之与不裁剪的地方做明暗对比。
(这里的遮罩层仅仅是用来做显示效果,并没有做裁剪图片的工作。不知道这一步能不能直接去掉?有知道的童鞋麻烦告诉下我。)

//绘制遮罩层:
t.editBox.height = t.imgHeight;
t.editBox.width = t.imgWidth;
t.editBox.style.display = 'block';
t.editBox.style.left = t.px;
t.editBox.style.top = t.py;
 
var cover = t.editBox.getContext("2d");
cover.fillStyle = "rgba(0, 0, 0, 0.5)";
cover.fillRect (0,0, t.imgWidth, t.imgHeight);
cover.clearRect(t.sx, t.sy, t.sHeight, t.sWidth);

第四步:使用canvas绘制裁剪后的图片
在第三步里面,把遮罩层绘制好了,但是遮罩层并没有裁剪的能力,仅仅是用来显示裁剪区域与非裁剪区域的对比而已,所以这里就开始做裁剪图片的功能了。同样使用到canvas的drawImage方法。

//绘制剪切图片:
t.editPic.height = t.sHeight;
t.editPic.width = t.sWidth;
var ctx = t.editPic.getContext('2d');
var images = new Image();
images.src = t.imgUrl;
images.onload = function(){
 ctx.drawImage(images,t.sx, t.sy, t.sHeight, t.sWidth, 0, 0, t.sHeight, t.sWidth); //裁剪图片
 document.getElementById('show_edit').getElementsByTagName('img')[0].src = t.editPic.toDataURL(); //把裁剪后的图片使用img标签显示出来
}

第五步:拖动裁剪框,重新裁剪图片
使用截图上传头像功能时我们希望能裁剪到满意的图片,所以裁剪框就需要不停的变动才得以裁剪出完美的图片。前几步已经把裁剪图片的基本功能做出来了,所以现在需要做的就是裁剪框跟进鼠标的移动来实时裁剪图片。

drag: function() {
 var t = this;
 var draging = false;
 var startX = 0;
 var startY = 0;
 
 document.getElementById('cover_box').onmousemove = function(e) {
  //获取鼠标到背景图片的距离
  var pageX = e.pageX - ( t.regional.offsetLeft + this.offsetLeft );
  var pageY = e.pageY - ( t.regional.offsetTop + this.offsetTop );
  //判断鼠标是否在裁剪区域里面:
  if ( pageX > t.sx && pageX < t.sx + t.sWidth && pageY > t.sy && pageY < t.sy + t.sHeight ) {
   this.style.cursor = 'move';
    
   this.onmousedown = function(){
    draging = true;
    //记录上一次截图的坐标
    t.ex = t.sx;
    t.ey = t.sy;
    //记录鼠标按下时候的坐标
    startX = e.pageX - ( t.regional.offsetLeft + this.offsetLeft );
    startY = e.pageY - ( t.regional.offsetTop + this.offsetTop );
   }
   window.onmouseup = function() {
    draging = false;
   }
    
   if (draging) {
    //移动时裁剪区域的坐标 = 上次记录的定位 + (当前鼠标的位置 - 按下鼠标的位置),裁剪区域不能超出遮罩层的区域;
    if ( t.ex + (pageX - startX) < 0 ) {
     t.sx = 0;
    } else if ( t.ex + (pageX - startX) + t.sWidth > t.imgWidth) {
     t.sx = t.imgWidth - t.sWidth;
    } else {
     t.sx = t.ex + (pageX - startX);
    };
 
    if (t.ey + (pageY - startY) < 0) {
     t.sy = 0;
    } else if ( t.ey + (pageY - startY) + t.sHeight > t.imgHeight ) {
     t.sy = t.imgHeight - t.sHeight;
    } else {
     t.sy = t.ey + (pageY - startY);
    }
 
    t.cutImage();
   }
  } else{
   this.style.cursor = 'auto';
  }
 };
}

大功告成,图片如下:

 JavaScript+html5 canvas实现本地截图教程

有童鞋指出,每移动一下鼠标就裁剪一张图片不是很耗性能吗,为什么不用background-position来做预览效果 保存的时候才用canvas裁出来?一听觉得这建议很有道理,所以就在第四步把代码稍微改动了一下。鼠标移动的时候的预览效果是改变图片的background-position,点击保存按钮的时候才裁剪图片,把裁剪下来的图片生成新的url就可以传给服务器啦~~
以下代码是改正过来的,大家有什么其它好的建议欢迎指出来喔 ^_^ ^_^
demo完整代码如下:
注意:因为用的是seajs写的,所以稍微留意下文件的加载情况啦
css:

body{text-align:center;}
#label{border:1px solid #ccc;background-color:#fff;text-align:center;height:300px; width:300px;margin:20px auto;position:relative;}
#get_image{position:absolute;}
#edit_pic{position:absolute;display:none;background:#000;}
#cover_box{position: absolute;z-index: 9999;display:none;top:0px;left:0px;}
#show_edit{margin: 0 auto;display:inline-block;}
#show_pic{height:100px;width:100px;border:2px solid #000;overflow:hidden;margin:0 auto;display:inline-block; }

html:

<input type="file" name="file" id="post_file">
<button id="save_button">保存</button>
<div id="label">
 <canvas id="get_image"></canvas>
 <p>
  <canvas id="cover_box"></canvas>
  <canvas id="edit_pic"></canvas>
 </p>
</div>
<p>
 <span id="show_edit"></span>
 <span id="show_pic"><img src=""></span>
</p>


<script type="text/javascript" src="../../lib/seajs/sea.js"></script>
<script type="text/javascript">
seajs.use(['_example/fileAPI/index_v2.js'], function(clipFile) {
 clipFile.init({
  clipPos: {  //裁剪框的默认尺寸与定位
   x: 15,
   y: 15,
   height: 100,
   width: 100,
  },
 });
});

</script> 

js:

define(function(require, exports, module) {

 'use strict';

 var postFile = {
  init: function(options) {
   var t = this;
   t.regional = document.getElementById('label');
   t.getImage = document.getElementById('get_image');
   t.clipPic = document.getElementById('edit_pic');
   t.coverBox = document.getElementById('cover_box');
   t.achieve = document.getElementById('show_edit');

   t.clipPos = options.clipPos;

   //初始化图片基本参数
   t.bgPagePos = {   
    x: 0,
    y: 0,
    height: 0,
    width: 0
   };

   //传进图片
   document.getElementById('post_file').addEventListener("change", t.handleFiles, false);

   //点击保存按钮后再裁剪图片
   document.getElementById('save_button').onclick = function() {

    //绘制剪切后的图片:
    t.clipPic.height = t.clipPos.height;
    t.clipPic.width = t.clipPos.width;

    var ctx = t.clipPic.getContext('2d');
    var images = new Image();
    images.src = t.imgUrl;
    images.onload = function(){

     //drawImage(images,相对于裁剪图片的X, 相对于裁剪图片的Y, 裁剪的高度, 裁剪的宽度, 显示在画布的X, 显示在画布的Y, 显示在画布多高, 显示在画布多宽);
     ctx.drawImage(images,t.clipPos.x, t.clipPos.y, t.clipPos.height, t.clipPos.width, 0, 0, t.clipPos.height, t.clipPos.width); //裁剪图片
     
     document.getElementById('show_pic').getElementsByTagName('img')[0].src = t.clipPic.toDataURL();
    }
   };

   t.drag();
  },
  handleFiles: function() {

   var fileList = this.files[0];
   var oFReader = new FileReader();

   //读取文件内容
   oFReader.readAsDataURL(fileList);

   //当读取操作成功完成时调用.
   oFReader.onload = function (oFREvent) { 

    //把预览图片URL传给函数
    postFile.paintImage(oFREvent.target.result);
   };
  },
  paintImage: function(url) {
   var t = this;
   var createCanvas = t.getImage.getContext("2d");

   var img = new Image();
   img.src = url;

   //把传进来的图片进行等比例缩放
   img.onload = function(){
    //等比例缩放图片(如果图片宽高都比容器小,则绘制的图片宽高 = 原图片的宽高。)
    //如果图片的宽度或者高度比容器大,则宽度或者高度 = 容器的宽度或者高度,另一高度或者宽度则等比例缩放

    //t.bgPagePos.width:绘制后图片的宽度;
    //t.bgPagePos.height:绘制后图片的高度;
    //t.bgPagePos.x:绘制后图片的X轴;
    //t.bgPagePos.y:绘制后图片的Y轴
    if ( img.width < t.regional.offsetWidth && img.height < t.regional.offsetHeight) {
     t.bgPagePos.width = img.width;
     t.bgPagePos.height = img.height;

    } else {
     var pWidth = img.width / (img.height / t.regional.offsetHeight);
     var pHeight = img.height / (img.width / t.regional.offsetWidth);

     t.bgPagePos.width = img.width > img.height ? t.regional.offsetWidth : pWidth;
     t.bgPagePos.height = img.height > img.width ? t.regional.offsetHeight : pHeight;
    }

    //图片的坐标
    t.bgPagePos.x = (t.regional.offsetWidth - t.bgPagePos.width) / 2 + 'px';
    t.bgPagePos.y = (t.regional.offsetHeight - t.bgPagePos.height) / 2 + 'px';
    
    t.getImage.height = t.bgPagePos.height;
    t.getImage.width = t.bgPagePos.width;
    t.getImage.style.left = t.bgPagePos.x;
    t.getImage.style.top = t.bgPagePos.y;

    createCanvas.drawImage(img,0,0,t.bgPagePos.width,t.bgPagePos.height);//没用直接插入背景图片而用canvas绘制图片,是为了调整所需框内图片的大小
    
    t.imgUrl = t.getImage.toDataURL();//储存canvas绘制的图片地址

    t.clipImg();
   };
  },
  clipImg: function() {
   var t = this;

   //绘制遮罩层:
   t.coverBox.height = t.bgPagePos.height;
   t.coverBox.width = t.bgPagePos.width;
   t.coverBox.style.display = 'block';
   t.coverBox.style.left = t.bgPagePos.x;
   t.coverBox.style.top = t.bgPagePos.y;

   var cover = t.coverBox.getContext("2d");
   cover.fillStyle = "rgba(0, 0, 0, 0.5)";
   cover.fillRect (0,0, t.bgPagePos.width, t.bgPagePos.height);
   cover.clearRect(t.clipPos.x, t.clipPos.y, t.clipPos.height, t.clipPos.width);

   t.achieve.style.background = 'url(' + t.imgUrl + ')' + -t.clipPos.x + 'px ' + -t.clipPos.y + 'px no-repeat';
   t.achieve.style.height = t.clipPos.height + 'px';
   t.achieve.style.width = t.clipPos.width + 'px';
  },
  drag: function() {
   var t = this;
   var draging = false;
   var _startPos = null;

   t.coverBox.onmousemove = function(e) {
    e = e || window.event;

    if ( e.pageX == null && e.clientX != null ) {

     var doc = document.documentElement, body = document.body;

     e.pageX = e.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
     e.pageY = e.clientY + (doc && doc.scrollTop || body && body.scrollTop);
    }

    //获取鼠标到背景图片的距离
    var _mousePos = {
     left: e.pageX - ( t.regional.offsetLeft + this.offsetLeft ),
     top: e.pageY - ( t.regional.offsetTop + this.offsetTop )
    }

    //判断鼠标是否在裁剪区域里面:
    if ( _mousePos.left > t.clipPos.x && _mousePos.left < t.clipPos.x + t.clipPos.width && _mousePos.top > t.clipPos.y && _mousePos.top < t.clipPos.y + t.clipPos.height ) {
     this.style.cursor = 'move';
     
     this.onmousedown = function(){
      draging = true;
      //记录上一次截图的坐标
      t.ex = t.clipPos.x; 
      t.ey = t.clipPos.y;

      //记录鼠标按下时候的坐标
      _startPos = {
       left: e.pageX - ( t.regional.offsetLeft + this.offsetLeft ),
       top: e.pageY - ( t.regional.offsetTop + this.offsetTop )
      }
     }

     if (draging) {
      //移动时裁剪区域的坐标 = 上次记录的定位 + (当前鼠标的位置 - 按下鼠标的位置),裁剪区域不能超出遮罩层的区域;
      if ( t.ex + ( _mousePos.left - _startPos.left ) < 0 ) {
       t.clipPos.x = 0;
      } else if ( t.ex + ( _mousePos.left - _startPos.left ) + t.clipPos.width > t.bgPagePos.width ) {
       t.clipPos.x = t.bgPagePos.width - t.clipPos.width;
      } else {
       t.clipPos.x = t.ex + ( _mousePos.left - _startPos.left );
      };

      if (t.ey + ( _mousePos.top - _startPos.top ) < 0) {
       t.clipPos.y = 0;
      } else if ( t.ey + ( _mousePos.top - _startPos.top ) + t.clipPos.height > t.bgPagePos.height ) {
       t.clipPos.y = t.bgPagePos.height - t.clipPos.height;
      } else {
       t.clipPos.y = t.ey + ( _mousePos.top - _startPos.top );
      }

      t.clipImg();
     }

     document.body.onmouseup = function() {
      draging = false;
      document.onmousemove = null;
      document.onmouseup = null;
     }
    } else{
     this.style.cursor = 'auto';
    }
   };
  }
 }
 return postFile;
});

以上就是本文的全部内容,希望对大家的学习有所帮助。

Javascript 相关文章推荐
防止动态加载JavaScript引起的内存泄漏问题
Oct 08 Javascript
jcarousellite.js 基于Jquery的图片无缝滚动插件
Dec 30 Javascript
bootstrap table 服务器端分页例子分享
Feb 10 Javascript
jQuery语法小结(超实用)
Dec 31 Javascript
基于JS实现的笛卡尔乘积之商品发布
May 13 Javascript
node koa2实现上传图片并且同步上传到七牛云存储
Jul 31 Javascript
angular5 子组件监听父组件传入值的变化方法
Sep 30 Javascript
AngularJS上传文件的示例代码
Nov 10 Javascript
js实现按钮开关单机下拉菜单效果
Nov 22 Javascript
JavaScript变量Dom对象的所有属性
Apr 30 Javascript
vue实现简易的双向数据绑定
Dec 29 Vue.js
游戏开发中如何使用CocosCreator进行音效处理
Apr 14 Javascript
javascript自定义滚动条实现代码
Apr 20 #Javascript
JavaScript File API实现文件上传预览
Feb 02 #Javascript
jQuery AjaxUpload 上传图片代码
Feb 02 #Javascript
js+html5操作sqlite数据库的方法
Feb 02 #Javascript
详解Webwork中Action 调用的方法
Feb 02 #Javascript
JavaScript File API文件上传预览
Feb 02 #Javascript
javascript绘制漂亮的心型线效果完整实例
Feb 02 #Javascript
You might like
PHP中数据库单例模式的实现代码分享
2014/08/21 PHP
PHP+MySQL之Insert Into数据插入用法分析
2015/09/27 PHP
转一个日期输入控件,支持FF
2007/04/27 Javascript
jQuery创建平滑的页面滚动(顶部或底部)
2013/02/26 Javascript
jquery验证表单中的单选与多选实例
2013/08/18 Javascript
TypeScript 学习笔记之基本类型
2015/06/19 Javascript
jQuery layui常用方法介绍
2016/07/25 Javascript
Vue.js实现模拟微信朋友圈开发demo
2017/04/20 Javascript
Javascript操作dom对象之select全面解析
2017/04/24 Javascript
Windows下快速搭建NodeJS本地服务器的步骤
2017/08/09 NodeJs
jQuery EasyUI Layout实现tabs标签的实例
2017/09/26 jQuery
vue路由嵌套的SPA实现步骤
2017/11/06 Javascript
vue.js配合$.post从后台获取数据简单demo分享
2018/08/11 Javascript
Vue指令指令大全
2019/02/09 Javascript
Vue实现简单计算器
2021/01/20 Vue.js
python uuid模块使用实例
2015/04/08 Python
Python中强大的命令行库click入门教程
2016/12/26 Python
Python中实现最小二乘法思路及实现代码
2018/01/04 Python
python实现对csv文件的列的内容读取
2018/07/04 Python
Python使用修饰器进行异常日志记录操作示例
2019/03/19 Python
Python3.7 基于 pycryptodome 的AES加密解密、RSA加密解密、加签验签
2019/12/04 Python
Python-numpy实现灰度图像的分块和合并方式
2020/01/09 Python
python+Selenium自动化测试——输入,点击操作
2020/03/06 Python
Django中使用Celery的方法步骤
2020/12/07 Python
python实现简单的井字棋游戏(gui界面)
2021/01/22 Python
澳大利亚首屈一指的在线购物目的地:Kogan.com
2017/02/02 全球购物
Clarks西班牙官方在线商店:clarks鞋
2019/05/03 全球购物
后勤自我鉴定
2013/10/13 职场文书
生产车间主管岗位职责
2013/12/28 职场文书
高三自我评价
2014/02/01 职场文书
军训学生自我鉴定
2014/02/12 职场文书
期末学生评语大全
2014/04/24 职场文书
质监局领导班子践行群众路线整改方案
2014/10/26 职场文书
财务人员岗位职责
2015/02/03 职场文书
幼儿园庆六一主持词
2015/06/30 职场文书
Win10系统搭建ftp文件服务器详细教程
2022/08/05 Servers