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 相关文章推荐
一个很简单的jquery+xml+ajax的无刷新树结构(无css,后台是c#)
Jun 02 Javascript
javascript获取网页中指定节点的父节点、子节点的方法小结
Apr 24 Javascript
js二维数组排序的简单示例代码
Jan 24 Javascript
JS根据年月获得当月天数的实现代码
Jul 03 Javascript
jQuery插件jFade实现鼠标经过的图片高亮其它变暗
Mar 14 Javascript
JavaScript检查数字是否为整数或浮点数的方法
Jun 09 Javascript
ionic实现带字的toggle滑动组件
Aug 27 Javascript
HTML5canvas 绘制一个圆环形的进度表示实例
Dec 16 Javascript
在javascript中,null>=0 为真,null==0却为假,null的值详解
Feb 22 Javascript
javascript九宫格图片随机打乱位置的实现方法
Mar 15 Javascript
websocket+node.js实现实时聊天系统问题咨询
May 17 Javascript
微信小程序实现的五星评价功能示例
Apr 25 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中stream(流)的用法
2014/03/25 PHP
php通过ajax实现双击table修改内容
2014/04/28 PHP
PHP判断IP并转跳到相应城市分站的方法
2015/03/25 PHP
微信公众号判断用户是否已关注php代码解析
2016/06/24 PHP
PHP使用php-resque库配合Redis实现MQ消息队列的教程
2016/06/29 PHP
php提交表单时保留多个空格及换行的文本样式的方法
2017/06/20 PHP
PHP7新功能总结
2019/04/14 PHP
laravel 框架结合关联查询 when()用法分析
2019/11/22 PHP
关于B/S判断浏览器断开的问题讨论
2008/10/29 Javascript
jquery ajax post提交数据乱码
2013/11/05 Javascript
解决jquery操作checkbox火狐下第二次无法勾选问题
2014/02/10 Javascript
javascript实现C语言经典程序题
2015/11/29 Javascript
基于jQuery实现滚动切换效果
2016/12/02 Javascript
H5上传本地图片并预览功能
2017/05/08 Javascript
手动用webpack搭建第一个ReactApp的示例
2018/04/11 Javascript
echarts设置图例颜色和地图底色的方法实例
2018/08/01 Javascript
详解vue项目中使用token的身份验证的简单实践
2019/03/08 Javascript
详解vue-video-player使用心得(兼容m3u8)
2019/08/23 Javascript
js DOM的事件常见操作实例详解
2019/12/16 Javascript
深入Python解释器理解Python中的字节码
2015/04/01 Python
python的exec、eval使用分析
2017/12/11 Python
python实现五子棋小游戏
2020/03/25 Python
Python图像处理之图片文字识别功能(OCR)
2019/07/30 Python
在keras里实现自定义上采样层
2020/06/28 Python
如何快速一次性卸载所有python包(第三方库)呢
2020/10/20 Python
python tqdm实现进度条的示例代码
2020/11/10 Python
斐乐美国官方网站:FILA美国
2019/03/01 全球购物
主持人演讲稿范文
2013/12/28 职场文书
保护母亲河倡议书
2014/04/14 职场文书
2014大学生职业生涯规划书最新范文
2014/09/13 职场文书
销售代理协议书
2014/09/30 职场文书
领导干部查摆“四风”问题自我剖析材料思想汇报
2014/10/05 职场文书
优秀班组事迹材料
2014/12/24 职场文书
读后感作文评语
2014/12/25 职场文书
Go语言grpc和protobuf
2022/04/13 Golang
Win11怎么解除儿童账号限制?Win11解除微软儿童账号限制方法
2022/07/07 数码科技