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 Object与Function使用
Jan 11 Javascript
js png图片(有含有透明)在IE6中为什么不透明了
Feb 07 Javascript
锋利的jQuery 第三章章节总结的例子
Mar 23 Javascript
html5 canvas js(数字时钟)实例代码
Dec 23 Javascript
jquery统计用户选中的复选框的个数
Jun 06 Javascript
JavaScript中Cookie操作实例
Jan 09 Javascript
jQuery实现contains方法不区分大小写的方法
Feb 13 Javascript
简介JavaScript中strike()方法的使用
Jun 08 Javascript
js获取浏览器的各种属性
Apr 27 Javascript
解决JS内存泄露之js对象和dom对象互相引用问题
Jun 25 Javascript
vue中如何让子组件修改父组件数据
Jun 14 Javascript
vue+django实现下载文件的示例
Mar 24 Vue.js
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 MSSQL 存储过程的方法
2008/12/24 PHP
php数组函数序列之array_sum() - 计算数组元素值之和
2011/10/29 PHP
PHP编程计算文件或数组中单词出现频率的方法
2017/05/22 PHP
js资料prototype 属性
2007/03/13 Javascript
TopList标签和JavaScript结合两例
2007/08/12 Javascript
JavaScript(js)设置默认输入焦点(focus)
2012/12/28 Javascript
可在线编辑网页文字效果代码(单击)
2013/03/02 Javascript
jQuery实现的简洁下拉菜单导航效果代码
2015/08/26 Javascript
jQuery Mobile动态刷新页面样式的实现方法
2016/05/28 Javascript
JavaScript自动点击链接 防止绕过浏览器访问的方法
2017/01/19 Javascript
webpack入门+react环境配置
2017/02/08 Javascript
switchery按钮的使用方法
2017/12/18 Javascript
angularjs使用div模拟textarea文本框的方法
2018/10/02 Javascript
使用 node.js 模仿 Apache 小部分功能
2019/07/07 Javascript
Javascript异步执行不按顺序解决方案
2020/04/30 Javascript
Vue watch响应数据实现方法解析
2020/07/10 Javascript
在vue项目中引用Antv G2,以饼图为例讲解
2020/10/28 Javascript
原生微信小程序开发中 redux 的使用详解
2021/02/18 Javascript
[36:33]完美世界DOTA2联赛循环赛 Matador vs Forest 第一场 11.06
2020/11/06 DOTA
理论讲解python多进程并发编程
2018/02/09 Python
python递归实现快速排序
2018/08/18 Python
代码详解django中数据库设置
2019/01/28 Python
Python3.5字符串常用操作实例详解
2019/05/01 Python
Python使用itcaht库实现微信自动收发消息功能
2020/07/13 Python
浅谈pandas dataframe对除数是零的处理
2020/07/20 Python
Pyecharts 中Geo函数常用参数的用法说明
2021/02/01 Python
阿玛瑞酒店中文官方网站:Amari.com
2018/02/13 全球购物
芬兰设计商店美国:Finnish Design Shop US
2019/03/25 全球购物
Linux操作面试题
2012/05/16 面试题
升职自荐信范文
2013/10/05 职场文书
教师学习培训邀请函
2014/02/04 职场文书
安康杯竞赛活动总结
2014/05/05 职场文书
教师批评与自我批评总结
2014/10/16 职场文书
心理健康教育主题班会
2015/08/13 职场文书
公司董事任命书
2015/09/21 职场文书
Go 中的空白标识符下划线
2022/03/25 Golang