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的固定表头和列头的代码
May 03 Javascript
Document:getElementsByName()使用方法及示例
Oct 28 Javascript
轻松使用jQuery双向select控件Bootstrap Dual Listbox
Dec 13 Javascript
基于javascript实现图片预加载
Jan 05 Javascript
Bootstrap~多级导航(级联导航)的实现效果【附代码】
Mar 08 Javascript
BootStrap智能表单实战系列(三)分块表单配置详解
Jun 13 Javascript
js倒计时简单实现代码
Aug 11 Javascript
Javascript this 函数深入详解
Dec 13 Javascript
js实现旋转木马效果
Mar 17 Javascript
详解babel升级到7.X采坑总结
May 12 Javascript
Vue2.0实现组件之间数据交互和通信操作示例
May 16 Javascript
详细分析vue表单数据的绑定
Jul 20 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和ACCESS写聊天室(二)
2006/10/09 PHP
PHP中echo,print_r与var_dump区别分析
2014/09/29 PHP
PHP表单验证内容是否为空的实现代码
2016/11/14 PHP
php对接java现实加签验签的实例
2016/11/25 PHP
利用jQuery接受和处理xml数据的代码(.net)
2011/03/28 Javascript
jquery实现漂亮的二级下拉菜单代码
2015/08/26 Javascript
JS字符串的切分用法实例
2016/02/22 Javascript
AngularJS表单基本操作
2017/01/09 Javascript
jQuery动态生成表格及右键菜单功能示例
2017/01/13 Javascript
Vue.js实现表格动态增加删除的方法(附源码下载)
2017/01/20 Javascript
Angular 容器部署的方法
2018/04/17 Javascript
详解Nuxt.js部署及踩过的坑
2018/08/07 Javascript
微信小程序实现类似微信点击语音播放效果
2020/03/30 Javascript
js仿360开机效果
2019/12/26 Javascript
在Python的while循环中使用else以及循环嵌套的用法
2015/10/14 Python
Python中的变量和作用域详解
2016/07/13 Python
使用python调用zxing库生成二维码图片详解
2017/01/10 Python
Python3爬虫爬取百姓网列表并保存为json功能示例【基于request、lxml和json模块】
2018/12/05 Python
Linux上使用Python统计每天的键盘输入次数
2019/04/17 Python
Python 3.8新特征之asyncio REPL
2019/05/28 Python
使用python实现简单五子棋游戏
2019/06/18 Python
python 模拟贷款卡号生成规则过程解析
2019/08/30 Python
python3爬取torrent种子链接实例
2020/01/16 Python
浅谈Django前端后端值传递问题
2020/07/15 Python
python判断变量是否为列表的方法
2020/09/17 Python
Anthropologie英国:美国家喻户晓的休闲服装和家居产品品牌
2018/12/05 全球购物
公共事业管理本科生求职信
2013/10/07 职场文书
即兴演讲稿
2014/01/04 职场文书
2014年上半年工作自我评价
2014/01/18 职场文书
大四自我鉴定
2014/02/08 职场文书
园林设计专业毕业生求职信
2014/03/23 职场文书
《厄运打不垮的信念》教学反思
2014/04/13 职场文书
汽车技术服务与贸易专业求职信
2014/07/20 职场文书
入党积极分子党支部意见
2015/06/02 职场文书
详解MySQL 联合查询优化机制
2021/05/10 MySQL
vue ref如何获取子组件属性值
2022/03/31 Vue.js