canvas粒子系统的构建,canvas学习笔记捌

By admin in 4858美高梅 on 2019年4月9日

前言

不管是做1些贰d的小游戏,也许制作小图标,可能抠图都急需用到这些功效,对图纸的背景举行透明化,是我们平常须要运用的1个功能。

常见状态下我们都会去下载PS只怕美图秀秀那样的软件去制作。

唯独自个儿实在不想单独为了做个透明图像就去下载那一个软件,这几个软件不仅容积大,要下载个半天,放在电脑上也占空间。

最要害的是每一遍自身做这么些工作,都需求去目前百度时而塑造透明图片的秘籍。

这一个软件即便强大,可是意义的不少仍然必要有的基础知识,往往造成了有的门道。

简短点说,就算瑞士联邦军刀很六,不过本人后天只供给一把起子,小编不想驾驭怎么蒙版图层,不想在一群什么美颜什么种种滤镜之中找半天,作者就想上传个图片点两下就好了。

那正是说能或不能够在线对图片进行背景透明化呢?

本来是有的,上边是网站

您觉得小编是来推举网址的?当然不是。

自个儿为此提到这一个网址,是因为本人此前就是用这几个做1些处理的,但是的确不是很给力啊。

canvas粒子系统的构建,canvas学习笔记捌。自家并不知道它的法则,也尚无看过它的代码,然而它的弱项很鲜明:

  1. 不能够对点名颜色实行透明化
  2. 当要求对色差极大的有余颜料实行透明化时,无能为力
  3. 对部分图片的透明化处理不够完善,会见世锯齿,不过又力不从心展开更为处理
  4. 对复杂图片完全无法

有失常态就化解难点呗,于是就有了前些天的小玩意儿。

ImageData对象

ImageData对象涵盖了3个区域内的canvas的像素新闻。它含有以下可读属性:

width
canvas的小幅度,单位是像素。
height
canvas的万丈,单位是像素。
data
3个Uint8ClampedArray的1维数组,包蕴了各类像素的奥德赛GBA值。

什么样是Uint捌ClampedArray?这一个数组里的数值是七个人的整型,而且值得范围在0和255中间。任何不在[0,
255]中间的数都会成为[0, 255]里面最相仿的不行整型数。
0到25伍之内,那记录的本来是酷威GBA颜色数值啦。这些data数组是这么排列的,data[0]是第1排第三列的像素Haval通道的数值,data[1]第3排第3列的像素G通道的数值,data[3]是首先排第三列的像素的Alpha通道的数值。而data[4]是首先排第三列的像素的Murano通道数值,以此类推。
比如,第50排第三00列的像素的高粱红通道的值:

blueComponent = imageData.data[((50 * (imageData.width * 4)) + (200
* 4)) + 2];

其它,data也有length属性,正是data数组的长短。

ImageData对象

ImageData对象涵盖了三个区域内的canvas的像素音信。它含有以下可读属性:

width
canvas的大幅,单位是像素。
height
canvas的惊人,单位是像素。
data
2个Uint八ClampedArray的1维数组,包涵了每一个像素的安德拉GBA值。

怎么样是Uint八ClampedArray?那些数组里的数值是六位的整型,而且值得范围在0和255以内。任何不在[0,
255]里头的数都会变成[0, 255]以内最周围的尤其整型数。
0到25伍以内,那记录的自然是PAJEROGBA颜色数值啦。这一个data数组是那样排列的,data[0]是率先排第二列的像素哈弗通道的数值,data[1]率先排第二列的像素G通道的数值,data[3]是第2排第3列的像素的Alpha通道的数值。而data[4]是首先排第一列的像素的PAJERO通道数值,以此类推。
例如,第60排第300列的像素的浅青通道的值:

blueComponent = imageData.data[((50 * (imageData.width * 4)) + (200
*4858美高梅, 4)) + 2];

其余,data也有length属性,正是data数组的尺寸。

日前的话

  本文将从最基本的imageData对象的理论知识说开去,详细介绍canvas粒子系统的构建

 

作品

与在此以前的创作一样,直接将效率写在这篇博客里了,所以能够一贯在新浪中选拔。

利用方法:

  1. 上传图片
  2. 点击图片,将以鼠标点击处的水彩为业内,对色差20之内的颜料进行透明化处理。(如果想调整色差标准,能够在决定台下设置transparentConfig.colorDiff)
  3. 对出现透明化处理有有误的地点,能够打开复苏模式,再一次移动鼠标到图片上,此时鼠标会成为浅蓝小方框,小方框区域内会来得原始图像。点击后会将暗黑小方框区域内的图像恢复生机为原始图像。(假使想调整小方框尺寸,能够在决定台下通过transparentConfig.setRecoverSize(30)的秘籍开展修改)
  4. 下载图片,化解。

自然根据本懒人的惯例,照旧只在chrome浏览器下降成,所以壹旦您用任何浏览器的话也许不可能正常操作。

然则本金和利息用的主导功效与过去一致都是足以在现世浏览器中实现的,只是供给您调一下包容性。

一旦你有闲情Mondeo,想商讨一下来说,这是本项目标
GitHub地址,为了能有益复制进腾讯网,所以代码是从来写在html中的。

少说废话,以下为使用:

选用背景图
开启恢复生机格局

 

创建ImageData对象

有两种方式成立ImageData:

var myImageData = ctx.createImageData(width, height);

var myImageData = ctx.createImageData(anotherImageData);

在意啊,方法2是不会把data属性复制过去的,仅仅是复制了大幅和中度,data数组里的像素音讯都以透明黑的,也正是都是0。

创建ImageData对象

有两种方法创造ImageData:

var myImageData = ctx.createImageData(width, height);

var myImageData = ctx.createImageData(anotherImageData);

小心啦,方法二是不会把data属性复制过去的,仅仅是复制了上涨幅度和惊人,data数组里的像素新闻都以透明黑的,也正是都是0。

效益演示

  上边是实例效果演示,博文结尾有100%源码

 

技术点

本金和利息用依旧只利用纯前端完成,涉及到的技术点如下:

  1. 取得图片文件
  2. 将文件转换为图片,并放入canvas中
  3. 点击canvas获取点击处的颜色新闻
  4. 据说钦定颜色,对图像中在色差范围内的水彩进行透明化处理
  5. 自定义鼠标,在鼠标上海展览中心示钦定区域内原有图像
  6. 对图像上钦点区域,实行图像恢复生机操作
  7. 下载图片

里头技术点1,二,7在事先的一篇博客中有关联到,所以那边就不再赘述,不打听的能够去看一下自家后面写的那篇博客:[在天涯论坛里给图片加水印(canvas

  • drag)]()

那么,接下去就让大家看一下现实的落实啊。

赢得像素消息

能够用getImageData()方法取得像素音讯。

var myImageData = ctx.getImageData(left, top, width, height);

top和left正是所截取的画布部分的左上角坐标。

Tip:
抢先画布区域重返的像素音信都以透明黑,也正是都是0。

赢得像素音讯

能够用getImageData()方法取得像素音信。

var myImageData = ctx.getImageData(left, top, width, height);

top和left便是所截取的画布部分的左上角坐标。

Tip:
超过画布区域重临的像素音信都以透明黑,也正是都以0。

imageData

  关于图像数据imageData共有二个章程,包括getImageData()、putImageData()、createImageData()

【getImageData()】

  2D上下文能够通过getImageData()取得原始图像数据。那么些法子接收5个参数:画面区域的x和y坐标以及该区域的像素宽度和可观

  例如,要收获左上角坐标为(十,伍)、大小为50*50像素的区域的图像数据,能够利用以下代码:

var imageData = context.getImageData(10,5,50,50);

  重返的靶子是ImageData的实例,种种ImageData对象有一脾本性:width\height\data

  一、width:表示imageData对角的上升幅度

  2、height:表示imageData对象的万丈

  3、data是三个数组,保存着图像中每3个像素的数码。在data数组中,每1个像素用多个要平素保存,分别代表red、green、blue、光滑度

  [注意]图像中有稍许像素,data的长度就优良像素个数乘以四

4858美高梅 1

//第一个像素如下
var data = imageData.data;
var red = data[0];
var green = data[1]; 
var blue = data[2];
var alpha = data[3];

4858美高梅 2

  数组中每一种成分的值是在0-255之间,能够直接待上访问到原来图像数据,就可见以种种艺术来操作这么些多少

  [注意]要是要采纳getImageData()获取的canvas中包罗drawImage()方法,则该办法中的U昂CoraL不可能跨域

【createImageData()】

  createImageData(width,height)方法创制新的空白ImageData对象。新对象的暗许像素值
transparent black,相当于rgba(0,0,0,0)

var imgData = context.createImageData(100,100);

【putImageData()】

  putImageData()方法将图像数据从钦点的ImageData对象放回画布上,该方法共有以下参数

4858美高梅 3

imgData:要放回画布的ImageData对象(必须)
x:imageData对象的左上角的x坐标(必须)
y:imageData对象的左上角的y坐标(必须)
dirtyX:在画布上放置图像的水平位置(可选)
dirtyY:在画布上放置图像的垂直位置(可选)
dirtyWidth:在画布上绘制图像所使用的宽度(可选)
dirtyHeight:在画布上绘制图像所使用的高度(可选)

4858美高梅 4

  [注意]参数3到7要么都未曾,要么都存在

context.putImageData(imgData,0,0);    
context.putImageData(imgData,0,0,50,50,200,200);

 

点击图片获取点击处的颜料新闻

经过type为file类型的input获取到文件,然后通过FileReader读取文件音信后放入到canvas中。

那是前两步所做的干活,今后大家须要做的是点击图像(实际上是canvas)获取到点击处的颜色音信。

先是大家要求获得到原来图像的像素音讯,并保存下去,这一步在图纸加载时落实,部分代码如下:

var ctx = document.getElementById('target_canvas').getContext('2d');
imgDataArr = ctx.getImageData(0, 0, imgWidth, imgHeight).data;

 小课堂开端:

canvas的getImageData会获得canvas中钦点区域内的图像消息,再次回到1个ImageData对象。

ImageData对象的data属性的值是八个Uint八ClampedArray对象,而那几个指标正是图像的像素消息。

Uint八ClampedArray看名字能够精通到,它是3个换汤不换药数组,里面包车型客车值都以0-25伍限量以内的值。

借使我们有一个图形唯有八个像素,长二px,宽2px。左上角的像素和右下角的像素为士林蓝,而右上角和左下角的像素为铁锈色。

如下图:

4858美高梅 5

那就是说那张图纸会以什么的款型储存在Uint8ClampedArray数组中吗?

先是大家了然到青绿的中华VGBA值为rgba(255,255,25五,255),玉米黄的科雷傲GBA值为rgba(0,0,0,255)。

那么那张图片的表明为rgba值,分别为

rgba(0,0,0,255)           rgba(255,255,255,255)
rgba(255,255,255,255)     rgba(0,0,0,255)

这正是说颜色值将会以从左到右,从上至下的方法存储到Uint八ClampedArray数组中,如下:

[0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,255]

小课堂教学停止,回到正题。

今昔大家早已获得了原本图像的像素消息了,并存放在了imgDataArr那几个Uint八ClampedArray数组中。

怎么取得鼠标点击处的像素消息吗?代码如下:

   /**
      * 获取图像数据中指定偏移处的颜色信息
      */
    function getColorInfo(imgDataArr, offsetX, offsetY) {
      var pos = canvasInfo.width * 4 * offsetY + offsetX * 4;
      return {
        rValue: imgDataArr[pos],
        gValue: imgDataArr[pos + 1],
        bValue: imgDataArr[pos + 2],
        aValue: imgDataArr[pos + 3]
      }
    }

    /**
     * 非恢复模式下,点击canvas,以点击处颜色为标准,去掉颜色色差在指定色差范围内的颜色
     */
    function transparetModeCanvasClick(e) {
      if (imgDataArr.length === 0) {
        return;
      }
      if (resultImgDataArr.length === 0) {
        resultImgDataArr = imgDataArr.slice(0)
      }
      var clickColorInfo = getColorInfo(resultImgDataArr, e.offsetX, e.offsetY)
      ...
    }

咱们会给canvas绑定回调函数为transparetModeCanvasClick的click事件,那么,在鼠标点击canvas后,大家就足以拿走到鼠标绝对于canvas左上角的点击地方。

imgDataArr里面保存的是原来的图像像素新闻,之后还会用到,所以那里不做处理。

那便是说就copy数据到当下像素音信数组resultImgDataArr中。

接下来拿走像素消息时须要计算像素在一维数组中的地点:

var pos = canvasInfo.width * 4 * offsetY + offsetX * 4;

基于上边的表达式获得点击的不得了像素在一维数组中的地方,要是有明细阅读之前Uint捌ClampedArray存款和储蓄像素音信的方式,那一个表明式应该简单驾驭。

例子

var img = new Image();
img.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg';
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
img.onload = function() {
  ctx.drawImage(img, 0, 0);
  img.style.display = 'none';
};
var color = document.getElementById('color');
function pick(event) {
  var x = event.layerX;
  var y = event.layerY;
  var pixel = ctx.getImageData(x, y, 1, 1);
  var data = pixel.data;
  var rgba = 'rgba(' + data[0] + ', ' + data[1] +
             ', ' + data[2] + ', ' + (data[3] / 255) + ')';
  color.style.background =  rgba;
  color.textContent = rgba;
}
canvas.addEventListener('mousemove', pick);

例子

var img = new Image();
img.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg';
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
img.onload = function() {
  ctx.drawImage(img, 0, 0);
  img.style.display = 'none';
};
var color = document.getElementById('color');
function pick(event) {
  var x = event.layerX;
  var y = event.layerY;
  var pixel = ctx.getImageData(x, y, 1, 1);
  var data = pixel.data;
  var rgba = 'rgba(' + data[0] + ', ' + data[1] +
             ', ' + data[2] + ', ' + (data[3] / 255) + ')';
  color.style.background =  rgba;
  color.textContent = rgba;
}
canvas.addEventListener('mousemove', pick);

粒子写入

  粒子,指图像数据imageData中的每三个像素点。上边以1个回顾实例来表明完全写入与粒子写入

【完全写入】

  200*200的canvas第11中学设有文字’小火柴’,并将canvas一方方面面作为图像数据写入同样尺寸的canvas第22中学

<canvas id="drawing1" style="border:1px solid black"></canvas>
<canvas id="drawing2" style="border:1px solid black"></canvas>
<script>
var drawing1 = document.getElementById('drawing1');
var drawing2 = document.getElementById('drawing2');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var cxt2 = drawing2.getContext('2d');
  var W = drawing1.width = drawing2.width = 200;
  var H = drawing1.height = drawing2.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh + 'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  //写入drawing2中 
  cxt2.putImageData(imageData,0,0);
</script>  

【粒子写入】

  对于截然写入而言,也正是只是简短的复制粘贴,假设要对每种像素点举办精细地控制,则要求采纳粒子写入。canvas第11中学设有着大批量的空白区域,唯有’小火柴’那四个字的区域是卓有成效的。于是,能够依照图像数据imageData中的折射率对粒子进行筛选,只筛选出反射率大于0的粒子

<canvas id="drawing1" style="border:1px solid black"></canvas>
<canvas id="drawing2" style="border:1px solid black"></canvas>
<script>
var drawing1 = document.getElementById('drawing1');
var drawing2 = document.getElementById('drawing2');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var cxt2 = drawing2.getContext('2d');
  var W = drawing1.width = drawing2.width = 200;
  var H = drawing1.height = drawing2.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh + 'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  //写入drawing2中 
  cxt2.putImageData(setData(imageData),0,0);
  function setData(imageData){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    for(var i = 0; i < W; i++){
      for(var j = 0; j < H ;j++){
        //data值中的红色值
        var k = 4*(i + j*W);
        //data值中的透明度
        if(imageData.data[k+3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
        }
      }
    }
    //40000 2336
    console.log(i*j,dots.length);
    //新建一个imageData,并将筛选后的粒子信息保存到新建的imageData中
    var oNewImage = cxt.createImageData(W,H);
    for(var i = 0; i < dots.length; i++){
      oNewImage.data[dots[i]+0] = imageData.data[dots[i]+0];
      oNewImage.data[dots[i]+1] = imageData.data[dots[i]+1];
      oNewImage.data[dots[i]+2] = imageData.data[dots[i]+2];
      oNewImage.data[dots[i]+3] = imageData.data[dots[i]+3];
    }
    return oNewImage;
  }
}
</script>  

  固然结果看上去相同,但canvas2只行使了canvas第11中学伍仟0个粒子中的2337个

 

判定颜色与钦赐颜色的色差,并做透明化处理

在赢获得点击像素的颜色新闻后,大家必要去遍历整个canvas的像素消息,对于色差小于内定范围的颜料做透明化处理。代码如下:

        /**
         * 获取图像数据指定位置颜色与指定颜色的色差
         */
        function getColorDiff(imgDataArr, pos, colorInfo) {
          var value = Math.pow(imgDataArr[pos] - colorInfo.rValue, 2) +
            Math.pow(imgDataArr[pos + 1] - colorInfo.gValue, 2) +
            Math.pow(imgDataArr[pos + 2] - colorInfo.bValue, 2);

          return Math.pow(value, 0.5);
        }
        /**
         * 设置图像数据指定位置为透明色
         */
        function setTransparent(imgDataArr, pos) {
          imgDataArr[pos] = 0;
          imgDataArr[pos + 1] = 0;
          imgDataArr[pos + 2] = 0;
          imgDataArr[pos + 3] = 0;
        }
        /**
         * 非恢复模式下,点击canvas,以点击处颜色为标准,去掉颜色色差在指定色差范围内的颜色
         */
        function transparetModeCanvasClick(e) {
          if (imgDataArr.length === 0) {
            return;
          }
          if (resultImgDataArr.length === 0) {
            resultImgDataArr = imgDataArr.slice(0)
          }
          var clickColorInfo = getColorInfo(resultImgDataArr, e.offsetX, e.offsetY)

          // 如果是透明颜色则不做处理
          if (clickColorInfo.aValue === 0) {
            return;
          }

          var ctx = document.getElementById('target_canvas').getContext('2d');
          for (var pos = 0, len = canvasInfo.width * canvasInfo.height * 4; pos < len; pos = pos + 4) {
            if (getColorDiff(resultImgDataArr, pos, clickColorInfo) < transparentConfig.colorDiff) {
              setTransparent(resultImgDataArr, pos);
            }
          }
          ctx.putImageData(new ImageData(resultImgDataArr, canvasInfo.width, canvasInfo.height), 0, 0);
          setCanvasImgToDownloadLink();
        }

 色差总括公式为将rgb八个值的颜色相减,将她们的平方和开展开药方即可。

那里实在存在1个优化点:

壹、平时大家设为透明色的颜料,每回实际上都相比较单纯,那么在此处能够设1个权且数组存放已经比较过色差的颜色。再度要对多个颜料比较色差前,能够查看颜色是不是在那么些数组中,从而幸免重复计算色差,因而对此大图像而言,存在许多像素点,那种多量总结能尽量收缩的话能够让操作越来越快。

2、这里的大循环也能够展开优化。倒序循环  + Duff’s Device能够拓展优化。

因此在此间强调那点,是因为在此处对大图进行割除单一颜色为透明色时,确实需求开支更加多时间。(但是小编还是能承受,所以一时半刻不理了)

有关设置图片为透明色,实际上只要求将

imgDataArr[pos + 3] = 0;

即可。
但是为了和壹般宣称的透明颜色保持一致,依然将别的多少个值都设为0。

将数据设为透明色的多寡以往,要求调用canvas的putImageData方法将全方位图像的数据设置到canvas中。

至此,我们做到了对图像进行透明化处理的上上下下经过。
而是仍旧不够,因为对此复杂图像而言,那种艺术处理起来太过粗鲁,不可能做到精细化的处理。
为此接下去大家要落实恢复生机情势,对于拍卖的不得了的地方开始展览回复原来图像的操作。

结果

4858美高梅 6
鼠标滑过就会议及展览示rgba值。

结果

4858美高梅 7
鼠标滑过就会显得rgba值。

粒子筛选

  当粒子完全写入时,与canvas复制粘贴的成效等同。而当粒子有所筛选时,则会并发局地新奇的作用

【按序筛选】

  由于获得粒龙时,使用的是上涨幅度值*中度值的重复循环,且都以加一的款型递增。尽管不是加1,而是加n,则能够实现按序筛选的效益

<canvas id="drawing1" style="border:1px solid black"></canvas>
<canvas id="drawing2" style="border:1px solid black"></canvas>
<div id="con">
  <button>1</button>
  <button>2</button>
  <button>3</button>
  <button>4</button>
  <button>5</button>
</div>
<script>
var oCon = document.getElementById('con');
oCon.onclick = function(e){
  e = e || event;
  var tempN = e.target.innerHTML;
  if(tempN){
    cxt2.clearRect(0,0,W,H);
    cxt2.putImageData(setData(imageData,Number(tempN)),0,0);
  }
}
var drawing1 = document.getElementById('drawing1');
var drawing2 = document.getElementById('drawing2');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var cxt2 = drawing2.getContext('2d');
  var W = drawing1.width = drawing2.width = 200;
  var H = drawing1.height = drawing2.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh + 'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  //写入drawing2中 
  cxt2.putImageData(setData(imageData,1),0,0);
  function setData(imageData,n){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    for(var i = 0; i < W; i+=n){
      for(var j = 0; j < H ;j+=n){
        //data值中的红色值
        var k = 4*(i + j*W);
        //data值中的透明度
        if(imageData.data[k+3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
        }
      }
    }
    //新建一个imageData,并将筛选后的粒子信息保存到新建的imageData中
    var oNewImage = cxt.createImageData(W,H);
    for(var i = 0; i < dots.length; i++){
      oNewImage.data[dots[i]+0] = imageData.data[dots[i]+0];
      oNewImage.data[dots[i]+1] = imageData.data[dots[i]+1];
      oNewImage.data[dots[i]+2] = imageData.data[dots[i]+2];
      oNewImage.data[dots[i]+3] = imageData.data[dots[i]+3];
    }
    return oNewImage;
  }
}
</script>  

  点击上边包车型大巴不等按钮,能够拿走差别档次的粒子筛选

【随机筛选】

  除了采取按序筛选,还能动用随机筛选。 通过重复循环获得的粒子的职责新闻,放到dots数组中。通过splice()方法开始展览筛选,将筛选后的地方消息放到新建的newDots数组中,然后再利用createImageData(),新建2个图像数据对象并回到

<canvas id="drawing1" style="border:1px solid black"></canvas>
<canvas id="drawing2" style="border:1px solid black"></canvas>
<div id="con">
  <button>1000</button>
  <button>2000</button>
  <button>3000</button>
  <button>4000</button>
</div>
<script>
var oCon = document.getElementById('con');
oCon.onclick = function(e){
  e = e || event;
  var tempN = e.target.innerHTML;
  if(tempN){
    cxt2.clearRect(0,0,W,H);
    cxt2.putImageData(setData(imageData,1,Number(tempN)),0,0);
  }
}
var drawing1 = document.getElementById('drawing1');
var drawing2 = document.getElementById('drawing2');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var cxt2 = drawing2.getContext('2d');
  var W = drawing1.width = drawing2.width = 200;
  var H = drawing1.height = drawing2.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh + 'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  //写入drawing2中 
  cxt2.putImageData(setData(imageData,1),0,0);
  function setData(imageData,n,m){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    for(var i = 0; i < W; i+=n){
      for(var j = 0; j < H ;j+=n){
        //data值中的红色值
        var k = 4*(i + j*W);
        //data值中的透明度
        if(imageData.data[k+3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
        }
      }
    }    
    //筛选粒子,仅保存m个到newDots数组中。如果不传入m,则不进行筛选
    var newDots = [];
    if(m && (dots.length > m)){
      for(var i = 0; i < m; i++){
        newDots.push(Number(dots.splice(Math.floor(Math.random()*dots.length),1)));
      }
    }else{
      newDots = dots;
    }    
    //新建一个imageData,并将筛选后的粒子信息保存到新建的imageData中
    var oNewImage = cxt.createImageData(W,H);
    for(var i = 0; i < newDots.length; i++){
      oNewImage.data[newDots[i]+0] = imageData.data[newDots[i]+0];
      oNewImage.data[newDots[i]+1] = imageData.data[newDots[i]+1];
      oNewImage.data[newDots[i]+2] = imageData.data[newDots[i]+2];
      oNewImage.data[newDots[i]+3] = imageData.data[newDots[i]+3];
    }
    return oNewImage;
  }
}
</script>  

  点击上面包车型地铁分裂按钮,能够筛选出相应数量的妄动的粒子

 

苏醒格局的追究

在复苏情势下,当大家鼠标移动到canvas上时,鼠标展现为一个小方块,小方框内是原始图像。
当大家点击鼠标时,小方框内的图像会另行覆盖到当下图像上,从而完毕苏醒原来图像的效益。

全篇下来,其实在这一个地点才起初显得有趣。

 最初方案(隐藏鼠标 + canvas) 

在想到通过这些艺术开始展览精细化操作之后,第叁感应是东躲江苏鼠标,并在移动鼠标时跟随多少个小的canvas,这一个小canvas中显得的是原本图像。

骨子里最开始也是如此做的,如若各位有趣味的话能够参考作者github上的付出方案,下面有其壹方案的兑现。

就算这些方案能够兑现效益,不过存在3个很扎眼的性质难题,操作起来会有顿卡的感觉。

缘由正是在进行鼠标移动的时候,会频仍的猜想图像新闻,再写入到小canvas中。

纵然如此后来加了个防抖函数,并将移动方框放在防抖函数外,使得浅灰黄小框能够即时移动,在那之中的内容不会并发顿卡,不过因为防抖函数的留存,必然会有少数非常的小延迟。

用作1个懒人其实自身认为做到那里就足以了,因为这么作者也得以用了。

不过,纠结了半天仍旧改了,那种卡顿实在是蛋疼得紧。

当今的方案(隐藏鼠标 + background-image) 

今日的方案是在鼠标移动时,隐藏鼠标,并在鼠标那里加一个div,div里面安装原始图像的背景图片。

在移动鼠标时,不仅会对鼠标的岗位展开双重总计(说是地点,实际上用的是translate,而不是top+left),还会对背景图片的地方展开重新计算,那样就能够达成平等的功效了。

因而这种方案,在本人的微处理器上移动红框和计量恢复图像的不二诀要很流利,完全感觉不到卡顿。

即使在低配电脑上有卡顿的情状,那里也能够加2个防抖函数来拍卖。

制图像素

ctx.putImageData(myImageData, dx, dy);

以此办法能够流传三个ImageData对象,然后把ImageData对象中的像素音信都画出来。dx,
dy是绘制的左上角坐标。
譬如我们能够逐一改变ImageData对象中的值,从而改变了全部图象的颜色,再把它画出来。
这有如何用吧?对于要对像素举行的操作来说,这很便宜。比如反色、去色等操作。

制图像素

ctx.putImageData(myImageData, dx, dy);

其一措施能够流传2个ImageData对象,然后把ImageData对象中的像素新闻都画出来。dx,
dy是绘制的左上角坐标。
诸如我们得以逐一改变ImageData对象中的值,从而改变了整个图象的水彩,再把它画出来。
那有啥样用呢?对于要对像素实行的操作来说,那很便宜。比如反色、去色等操作。

像素显字

  下边来选择粒子筛选来达成七个像素显字的服从。像素显字即未有清晰的效果日趋过渡到完全展现

【按序像素显字】

  按序像素显字的达成原理相当不难,比如,共有3000个粒子,共13个水平的连结效果。则动用十二个数组,分别保存200,400,600,800,100,1200,1400,1600,1800和三千个粒子。然后采取定时器将其逐步展现出来即可

<canvas id="drawing1" style="border:1px solid black"></canvas>
<button id="btn">开始显字</button>
<script>
var drawing1 = document.getElementById('drawing1');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var W = drawing1.width = 200;
  var H = drawing1.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh + 'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  cxt.clearRect(0,0,W,H);
  //获得10组粒子
  var imageDataArr = [];
  var n = 10;
  var index = 0;
  for(var i = n; i > 0; i--){
    imageDataArr.push(setData(imageData,i));
  }
  var oTimer = null;
  btn.onclick = function(){
    clearTimeout(oTimer);
    showData();
  }
  function showData(){
    oTimer = setTimeout(function(){
      cxt.clearRect(0,0,W,H);
      //写入drawing1中 
      cxt.putImageData(imageDataArr[index++],0,0); 
      //迭代函数       
      showData();      
      if(index == 10){
       index = 0;
        clearTimeout(oTimer);
      }      

    },100);      
  }    
  function setData(imageData,n,m){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    for(var i = 0; i < W; i+=n){
      for(var j = 0; j < H ;j+=n){
        //data值中的红色值
        var k = 4*(i + j*W);
        //data值中的透明度
        if(imageData.data[k+3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
        }
      }
    }    
    //筛选粒子,仅保存m个到newDots数组中。如果不传入m,则不进行筛选
    var newDots = [];
    if(m && (dots.length > m)){
      for(var i = 0; i < m; i++){
        newDots.push(Number(dots.splice(Math.floor(Math.random()*dots.length),1)));
      }
    }else{
      newDots = dots;
    }    
    //新建一个imageData,并将筛选后的粒子信息保存到新建的imageData中
    var oNewImage = cxt.createImageData(W,H);
    for(var i = 0; i < newDots.length; i++){
      oNewImage.data[newDots[i]+0] = imageData.data[newDots[i]+0];
      oNewImage.data[newDots[i]+1] = imageData.data[newDots[i]+1];
      oNewImage.data[newDots[i]+2] = imageData.data[newDots[i]+2];
      oNewImage.data[newDots[i]+3] = imageData.data[newDots[i]+3];
    }
    return oNewImage;
  }
}
</script>  

  点击开始显字,即可出现效果

【随机像素显字】

  随机像素显字的原理类似,保存八个不一致数额的随意像素的数组即可

<canvas id="drawing1" style="border:1px solid black"></canvas>
<button id="btn">开始显字</button>
<script>
var drawing1 = document.getElementById('drawing1');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var W = drawing1.width = 200;
  var H = drawing1.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh + 'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  cxt.clearRect(0,0,W,H);
  //获得10组粒子
  var imageDataArr = [];
  var n = 10;
  var index = 0;
  for(var i = n; i > 0; i--){
    imageDataArr.push(setData(imageData,1,i));
  }
  var oTimer = null;
  btn.onclick = function(){
    clearTimeout(oTimer);
    showData();
  }
  function showData(){
    oTimer = setTimeout(function(){
      cxt.clearRect(0,0,W,H);
      //写入drawing1中 
      cxt.putImageData(imageDataArr[index++],0,0); 
      //迭代函数       
      showData();      
      if(index == 10){
        clearTimeout(oTimer);
        index = 0;
      }      
    },100);      
  }    
  function setData(imageData,n,m){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    for(var i = 0; i < W; i+=n){
      for(var j = 0; j < H ;j+=n){
        //data值中的红色值
        var k = 4*(i + j*W);
        //data值中的透明度
        if(imageData.data[k+3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
        }
      }
    }    
    //筛选粒子,仅保存dots.length/m个到newDots数组中
    var newDots = [];
    var len = Math.floor(dots.length/m);
    for(var i = 0; i < len; i++){
      newDots.push(Number(dots.splice(Math.floor(Math.random()*dots.length),1)));
    }
    //新建一个imageData,并将筛选后的粒子信息保存到新建的imageData中
    var oNewImage = cxt.createImageData(W,H);
    for(var i = 0; i < newDots.length; i++){
      oNewImage.data[newDots[i]+0] = imageData.data[newDots[i]+0];
      oNewImage.data[newDots[i]+1] = imageData.data[newDots[i]+1];
      oNewImage.data[newDots[i]+2] = imageData.data[newDots[i]+2];
      oNewImage.data[newDots[i]+3] = imageData.data[newDots[i]+3];
    }
    return oNewImage;
  }
}
</script> 

 

复苏格局的兑现

那正是说让大家以往来看一看苏醒格局下鼠标在canvas上运动时的代码:

// 根据鼠标的偏移位置获取recover_img位置
function getRecoverImgPos(e) {
  // 给鼠标位置+1,是为了让recover_img不会出现在鼠标下方,从而使得鼠标点击时不会点击在recover_img上
  return {
    x: e.offsetX + 1,
    y: e.offsetY + 1
  }
}

/**
 * 恢复模式下,鼠标在canvas上移动,呈现原先图像
 */
function recoverModeCanvasMove(e) {
  if (imgDataArr.length === 0) {
    return;
  }
  var $recoverImg = $("#recover_img");
  var recoverImgPos = getRecoverImgPos(e)
  if (recoverImgPos.x > canvasInfo.width - recoverSize || recoverImgPos.y > canvasInfo.height - recoverSize) {
  $recoverImg.hide();
    return;
  } else {
    $recoverImg.show();
  }
  $recoverImg.css({
    transform: 'translate(' + recoverImgPos.x + 'px,' + recoverImgPos.y + 'px)',
    'background-position': (-recoverImgPos.x - 1) + 'px ' + (-recoverImgPos.y - 1) + 'px'
  });
}

在上边的代码中大家会依照鼠标的地点再一次总计复苏图像所在的div的岗位,然后判断是或不是触边来支配是还是不是隐身。
跟着再来总计其地点。

此地有三个坑点在诠释里面也写了,正是回复图像实际上并不曾和鼠标重叠,以预防大家在点击时点到回复图像上而不是canvas上。

其它假若我们仔细查看css样式的话,会发现五个小坑点:

  1. 在样式里面对复苏图像和canvas上的鼠标准样品式都设置了隐藏,原因是防止当鼠标拖动过快时鼠标会油不过生在回复图像上,现身鼠标闪烁情形
  2. 回复图像本人就有3个top:1px和left:一px的上马值,那是因为大家的canvas有四个一px的border,而相对定位的地方是相对于canvas的父级的。

下一场再来看看点击恢复生机图像的代码:

/**
 * 恢复模式下,点击canvas,将点击处指定范围内图像恢复原样
 */
function recoverModeCanvasClick(e) {
  if (imgDataArr.length === 0) {
    return;
  }
  var recoverImgPos = getRecoverImgPos(e);
  for (var i = 0, ylen = recoverSize; i < ylen; i++) {
    var pos = canvasInfo.width * 4 * (recoverImgPos.y + i) + recoverImgPos.x * 4;
    for (var j = pos, xlen = pos + recoverSize * 4; j < xlen; j++) {
      resultImgDataArr[j] = imgDataArr[j]
    }
  }
  var ctx = document.getElementById('target_canvas').getContext('2d');
  ctx.putImageData(new ImageData(resultImgDataArr, canvasInfo.width, canvasInfo.height), 0, 0);
  setCanvasImgToDownloadLink()
}

壹样是先依照鼠标地方获取恢复图像的地点,然后依照偏移量去总计地点。

大家得小心到纵然我们的还原图像是壹块完整的相连接的区域,但是在
Uint八ClampedArray数组中的数据并不是相连接的,要求大家去总结。

将最伊始大家保留起来图像像素数组imgDataArr赋值到如今拍卖的图像像素数组中。

聊到底将拍卖好的像素数组以putImageData的办法放入数组即可。

例子

var img = new Image();
img.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg';
img.onload = function() {
  draw(this);
};

function draw(img) {
  var canvas = document.getElementById('canvas');
  var ctx = canvas.getContext('2d');
  ctx.drawImage(img, 0, 0);
  img.style.display = 'none';
  var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  var data = imageData.data;

  var invert = function() {
    for (var i = 0; i < data.length; i += 4) {
      data[i]     = 255 - data[i];     // red
      data[i + 1] = 255 - data[i + 1]; // green
      data[i + 2] = 255 - data[i + 2]; // blue
    }
    ctx.putImageData(imageData, 0, 0);
  };

  var grayscale = function() {
    for (var i = 0; i < data.length; i += 4) {
      var avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
      data[i]     = avg; // red
      data[i + 1] = avg; // green
      data[i + 2] = avg; // blue
    }
    ctx.putImageData(imageData, 0, 0);
  };

  var invertbtn = document.getElementById('invertbtn');
  invertbtn.addEventListener('click', invert);
  var grayscalebtn = document.getElementById('grayscalebtn');
  grayscalebtn.addEventListener('click', grayscale);
}

invert()反色函数将奇骏GB通道的值用25五减去,grayscale()去色函数将宝马X5GB通道的值实行平均,从而使颜色变成珍珠白。

例子

var img = new Image();
img.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg';
img.onload = function() {
  draw(this);
};

function draw(img) {
  var canvas = document.getElementById('canvas');
  var ctx = canvas.getContext('2d');
  ctx.drawImage(img, 0, 0);
  img.style.display = 'none';
  var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  var data = imageData.data;

  var invert = function() {
    for (var i = 0; i < data.length; i += 4) {
      data[i]     = 255 - data[i];     // red
      data[i + 1] = 255 - data[i + 1]; // green
      data[i + 2] = 255 - data[i + 2]; // blue
    }
    ctx.putImageData(imageData, 0, 0);
  };

  var grayscale = function() {
    for (var i = 0; i < data.length; i += 4) {
      var avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
      data[i]     = avg; // red
      data[i + 1] = avg; // green
      data[i + 2] = avg; // blue
    }
    ctx.putImageData(imageData, 0, 0);
  };

  var invertbtn = document.getElementById('invertbtn');
  invertbtn.addEventListener('click', invert);
  var grayscalebtn = document.getElementById('grayscalebtn');
  grayscalebtn.addEventListener('click', grayscale);
}

invert()反色函数将宝马X5GB通道的值用255减去,grayscale()去色函数将汉兰达GB通道的值进行平均,从而使颜色变成深橙。

粒子动画

  粒子动画并不是粒子在做动画,而是经过getImageData()方法得到粒子的轻易坐标和末段坐标后,通过fillRect()方法绘制的小方块在做活动。使用定时器,不断的绘图坐标变化的小方块,以此来发生运动的成效

【随飞机地点置】

<canvas id="drawing1" style="border:1px solid black"></canvas>
<button id="btn1">开始显字</button>
<button id="btn2">重新混乱</button>
<script>
var drawing1 = document.getElementById('drawing1');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var W = drawing1.width = 200;
  var H = drawing1.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh + 'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  cxt.clearRect(0,0,W,H);
  function setData(imageData,n,m){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    //dots的索引
    var index = 0;
    for(var i = 0; i < W; i+=n){
      for(var j = 0; j < H ;j+=n){
        //data值中的红色值
        var k = 4*(i + j*W);
        //data值中的透明度
        if(imageData.data[k+3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
          dots[index++] = {
            'index':index,
            'x':i,
            'y':j,
            'red':k,
            'randomX':Math.random()*W,
            'randomY':Math.random()*H,
          }
        }
      }
    }    
    //筛选粒子,仅保存dots.length/m个到newDots数组中
    var newDots = [];
    var len = Math.floor(dots.length/m);
    for(var i = 0; i < len; i++){
      newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]);
    }
    return newDots;
  }
  //获得粒子数组
  var dataArr = setData(imageData,1,1);
  var oTimer1 = null;
  var oTimer2 = null;
  btn1.onclick = function(){
    clearTimeout(oTimer1);
    showData(10);
  }  
  btn2.onclick = function(){
    clearTimeout(oTimer2);
    showRandom(10);
  }    
  function showData(n){
    oTimer1 = setTimeout(function(){
      cxt.clearRect(0,0,W,H);
      for(var i = 0; i < dataArr.length; i++){
        var temp = dataArr[i];
        var x0 = temp.randomX;
        var y0 = temp.randomY;
        var disX = temp.x - temp.randomX;
        var disY = temp.y - temp.randomY;
        cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1);  
      }   
      showData(n-1);    
      if(n === 1){
        clearTimeout(oTimer1);
      }      
    },60);  
  } 
  function showRandom(n){
    oTimer2 = setTimeout(function fn(){
      cxt.clearRect(0,0,W,H);
      for(var i = 0; i < dataArr.length; i++){
        var temp = dataArr[i];
        var x0 = temp.x;
        var y0 = temp.y;
        var disX = temp.randomX - temp.x;
        var disY = temp.randomY - temp.y;
        cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1);             
      }     
      showRandom(n-1);    
      if(n === 1){
        clearTimeout(oTimer2);
      }      
    },60);  
  } 
}
</script>  

【飘入效果】 

   飘入效果与自由显字的规律相似,不再赘言

<canvas id="drawing1" style="border:1px solid black"></canvas>
<button id="btn1">左上角飘入</button>
<script>
var drawing1 = document.getElementById('drawing1');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var W = drawing1.width = 200;
  var H = drawing1.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh + 'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  cxt.clearRect(0,0,W,H);
  function setData(imageData,n,m){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    //dots的索引
    var index = 0;
    for(var i = 0; i < W; i+=n){
      for(var j = 0; j < H ;j+=n){
        //data值中的红色值
        var k = 4*(i + j*W);
        //data值中的透明度
        if(imageData.data[k+3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
          dots[index++] = {
            'index':index,
            'x':i,
            'y':j,
            'red':k,
            'randomX':Math.random()*W,
            'randomY':Math.random()*H,
          }
        }
      }
    }    
    //筛选粒子,仅保存dots.length/m个到newDots数组中
    var newDots = [];
    var len = Math.floor(dots.length/m);
    for(var i = 0; i < len; i++){
      newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]);
    }
    return newDots;
  }
  //获得粒子数组
  var dataArr = setData(imageData,1,1);
  var oTimer1 = null;
  btn1.onclick = function(){
    clearTimeout(oTimer1);
    showData(10);
  }    
  function showData(n){
    oTimer1 = setTimeout(function(){
      cxt.clearRect(0,0,W,H);
      for(var i = 0; i < dataArr.length; i++){
        var temp = dataArr[i];
        var x0 = 0;
        var y0 = 0;
        var disX = temp.x - 0;
        var disY = temp.y - 0;
        cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1);  
      }   
      showData(n-1);    
      if(n === 1){
        clearTimeout(oTimer1);
      }      
    },60);  
  } 
}
</script>  

 

总结

本条小应用今后得以满足本身的必要了,不过它照旧存在不少欠缺与校正空间,比如:

  1. 兼容性
  2. 色差的调整,今后是位于控制台之中。固然是拿出来也恐怕只是八个输入框。实际上那个地方是足以做的越发健全,在答复三个犬牙相错图片时,当我们点击多个像素后,能够保存那么些像素,并出现2个调动色差的滑动条,拖动那些滑动条图像会指向色差在原基础上实时举行透明化和回复图像的拍卖。
  3. 卷土重来图像尺寸的调整,能够由此鼠标滑轮滚动的格局举办拍卖
  4. 归来操作。能够追加回退功效。
  5. 壹如既往存在的壹些小bug以及优化空间,以及照旧不够智能不够健全的图像处理,因为作者的确只想点一下然后就机关处理好。

本期分享甘休,依旧是3个小应用,仍旧是一批你恐怕掌握也大概不明白的小知识点。

终极例行表达,右下角的Smart球是点赞,那早正是本人连连几篇小说表达了,并且那么大多少个字已经写明了。
因为有多少个园友引用了那么些Smart球,所以你们应当也形成了Smart球正是点赞的思想预期和用户习惯了吗。
假定您非常大心点错了,请刷新页面,那么Smart球那里会产出废除点赞的按钮。

下次不会再解释了。

结果

4858美高梅 8

点击grayscale按钮图片会化为赫色,点击invert按钮图片会反色。
能够把代码放进codepen里看看效果怎么着。

结果

4858美高梅 9

点击grayscale按钮图片会化为土灰,点击invert按钮图片会反色。
能够把代码放进codepen里看看效果如何。

鼠标交互

  一般地,粒子的鼠标交互都与isPointIn帕特h(x,y)方法有关

【移入变色】

  当鼠标接近粒马时,该粒子变红。完结原理很简短。鼠标移动时,通过isPointInPath(x,y)方法检查实验,有啥粒子处于当前指针范围内。假若处在,绘制1像素的革命矩形即可

<canvas id="drawing1" style="border:1px solid black"></canvas>
<script>
var drawing1 = document.getElementById('drawing1');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var W = drawing1.width = 200;
  var H = drawing1.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh + 'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  function setData(imageData,n,m){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    //dots的索引
    var index = 0;
    for(var i = 0; i < W; i+=n){
      for(var j = 0; j < H ;j+=n){
        //data值中的红色值
        var k = 4*(i + j*W);
        //data值中的透明度
        if(imageData.data[k+3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
          dots[index++] = {
            'index':index,
            'x':i,
            'y':j,
            'red':k,
            'randomX':Math.random()*W,
            'randomY':Math.random()*H,
          }
        }
      }
    }    
    //筛选粒子,仅保存dots.length/m个到newDots数组中
    var newDots = [];
    var len = Math.floor(dots.length/m);
    for(var i = 0; i < len; i++){
      newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]);
    }
    return newDots;
  }
  //获得粒子数组
  var dataArr = setData(imageData,1,1);  
  //鼠标移动时,当粒子距离鼠标指针小于10时,则进行相关操作
  drawing1.onmousemove = function(e){
    e = e || event;
    var x = e.clientX - drawing1.getBoundingClientRect().left;
    var y = e.clientY - drawing1.getBoundingClientRect().top;
    cxt.beginPath();
    cxt.arc(x,y,10,0,Math.PI*2);
    for(var i = 0; i < dataArr.length; i++){
      var temp = dataArr[i];
      if(cxt.isPointInPath(temp.x,temp.y)){   
        cxt.fillStyle = 'red';
        cxt.fillRect(temp.x,temp.y,1,1);
      }        
    }   
  }
}
</script> 

  鼠标靠近文字时,可将接近文字的区域成为深黄

 【远离鼠标】

  鼠标点击时,以鼠标指针为圆心的任其自流限制内的粒子要求活动到该限制以外。一段时间后,粒子回到原有位置

  完成原理并不复杂,使用isPointInPath(x,y)方法即可,假使粒子处于当前路线中,则沿着鼠标指针与粒子坐标组成的直线方向,移动到路径的边缘

<canvas id="drawing1" style="border:1px solid black"></canvas>
<script>
var drawing1 = document.getElementById('drawing1');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var W = drawing1.width = 200;
  var H = drawing1.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh + 'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  //渲染文字
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  cxt.clearRect(0,0,W,H);
  function setData(imageData,n,m){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    //dots的索引
    var index = 0;
    for(var i = 0; i < W; i+=n){
      for(var j = 0; j < H ;j+=n){
        //data值中的红色值
        var k = 4*(i + j*W);
        //data值中的透明度
        if(imageData.data[k+3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
          dots[index++] = {
            'index':index,
            'x':i,
            'y':j,
            'red':k,
            'randomX':Math.random()*W,
            'randomY':Math.random()*H,
            'mark':false
          }
        }
      }
    }    
    //筛选粒子,仅保存dots.length/m个到newDots数组中
    var newDots = [];
    var len = Math.floor(dots.length/m);
    for(var i = 0; i < len; i++){
      newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]);
    }
    return newDots;
  }
  //获得粒子数组
  var dataArr = setData(imageData,2,1); 
  //将筛选后的粒子信息保存到新建的imageData中
  var oNewImage = cxt.createImageData(W,H);
  for(var i = 0; i < dataArr.length; i++){
    for(var j = 0; j < 4; j++){
      oNewImage.data[dataArr[i].red+j] = imageData.data[dataArr[i].red+j];
    }
  }    
  //写入canvas中
  cxt.putImageData(oNewImage,0,0);
  //设置鼠标检测半径为r
  var r = 20;
  //鼠标移动时,当粒子距离鼠标指针小于20时,则进行相关操作
  drawing1.onmousedown = function(e){
    e = e || event;
    var x = e.clientX - drawing1.getBoundingClientRect().left;
    var y = e.clientY - drawing1.getBoundingClientRect().top;
    cxt.beginPath();
    cxt.arc(x,y,r,0,Math.PI*2);
    for(var i = 0; i < dataArr.length; i++){
      var temp = dataArr[i];
      if(cxt.isPointInPath(temp.x,temp.y)){  
        temp.mark = true;
        var angle = Math.atan2((temp.y - y),(temp.x - x));
        temp.endX =  x - r*Math.cos(angle);
        temp.endY =  y - r*Math.sin(angle);
        var disX = temp.x - temp.endX;
        var disY = temp.y - temp.endY;
        cxt.fillStyle = '#fff';
        cxt.fillRect(temp.x,temp.y,1,1);
        cxt.fillStyle = '#000';
        cxt.fillRect(temp.endX,temp.endY,1,1);  
        dataRecovery(10);
      }else{
        temp.mark = false;
      }     
    }
    var oTimer = null;
    function dataRecovery(n){
      clearTimeout(oTimer);
      oTimer = setTimeout(function(){
        cxt.clearRect(0,0,W,H);
        for(var i = 0; i < dataArr.length; i++){
          var temp = dataArr[i];
          if(temp.mark){
            var x0 = temp.endX;
            var y0 = temp.endY;
            var disX = temp.x - x0;
            var disY = temp.y - y0;
            cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1);  
          }else{
            cxt.fillRect(temp.x,temp.y,1,1);
          }
        }   
        dataRecovery(n-1);    
        if(n === 1){
          clearTimeout(oTimer);
        }      
      },17);
    } 
  }  
}
</script>  

  使用鼠标点击canvas中的文字,出现效果

 

保留图片

canvas能够将画布保存成图片链接,图片链接能够用于标签大概下载。

canvas.toDataURL(‘image/png’)
默许转换来的格式是png。
canvas.toDataURL(‘image/jpeg’, quality)
也得以是其余图片格式。quality是图片品质,数值是0~一。0是最差的灵魂,一是最棒的灵魂。

也能够转变2进制对象:

canvas.toBlob(callback, type, encoderOptions)

保留图片

canvas能够将画布保存成图片链接,图片链接能够用来标签大概下载。

canvas.toDataURL(‘image/png’)
私下认可转换来的格式是png。
canvas.toDataURL(‘image/jpeg’, quality)
也能够是别的图片格式。quality是图片质量,数值是0~1。0是最差的格调,一是极品的格调。

也能够扭转二进制对象:

canvas.toBlob(callback, type, encoderOptions)

总结实例

  下边将上面的成效制作为一个可编写制定的归结实例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
<canvas id="drawing1" style="border:1px solid black"></canvas>
<br>
<div style="margin-bottom:10px">
  粒子设置:
  <input type="text" id="textValue" value="小火柴的蓝色理想">  
  <button id="btnSetText">文字设置确认</button>
  <button id="btnchoose2">按序筛选</button>
  <button id="btnchoose3">随机筛选</button>
  <button id="btnchoose1">不筛选</button>  
</div>
<div style="margin-bottom:10px">
  粒子效果:
  <button id="btn1">按序显字</button>
  <button id="btn2">随机显字</button>  
  <button id="btn3">混乱聚合</button>
  <button id="btn4">重新混乱</button>
</div>
<div>
  鼠标效果:
  1、鼠标移到文字上时,文字颜色变红;
  2、鼠标在文字上点击时,粒子远离鼠标指针
</div>
<script>
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var W = drawing1.width = 300;
  var H = drawing1.height = 200; 
  var imageData;
  var dataArr;
  btnSetText.onclick = function(){
    fnSetText(textValue.value);
  }  
  function fnSetText(str){
    cxt.clearRect(0,0,W,H);
    cxt.textBaseline = 'top';
    var sh = 60;
    cxt.font = sh + 'px  宋体'
    var sw = cxt.measureText(str).width;
    if(sw > W){
        sw = W;
    }
    cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);  
    imageData = cxt.getImageData(0,0,W,H); 
    dataArr = setData(imageData,1,1); 
  }
  fnSetText('小火柴');
  btnchoose1.onclick = function(){
    dataArr = setData(imageData,1,1);
    saveData(dataArr);  
  }
  btnchoose2.onclick = function(){
    dataArr = setData(imageData,2,1);
    saveData(dataArr); 
  }
  btnchoose3.onclick = function(){
    dataArr = setData(imageData,1,2);
    saveData(dataArr); 
  }    
  //筛选粒子
  function setData(imageData,n,m){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    //dots的索引
    var index = 0;
    for(var i = 0; i < W; i+=n){
      for(var j = 0; j < H ;j+=n){
        //data值中的红色值
        var k = 4*(i + j*W);
        //data值中的透明度
        if(imageData.data[k+3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
          dots[index++] = {
            'index':index,
            'x':i,
            'y':j,
            'red':k,
            'green':k+1,
            'blue':k+2,
            'randomX':Math.random()*W,
            'randomY':Math.random()*H,
            'mark':false
          }
        }
      }
    }    
    //筛选粒子,仅保存dots.length/m个到newDots数组中
    var newDots = [];
    var len = Math.floor(dots.length/m);
    for(var i = 0; i < len; i++){
      newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]);
    }
    return newDots;
  }
  function saveData(dataArr){
    //将筛选后的粒子信息保存到新建的imageData中
    var oNewImage = cxt.createImageData(W,H);
    for(var i = 0; i < dataArr.length; i++){
      for(var j = 0; j < 4; j++){
        oNewImage.data[dataArr[i].red+j] = imageData.data[dataArr[i].red+j];
      }
    }
    //写入canvas中
    cxt.putImageData(oNewImage,0,0);       
  }
  //显示粒子
  function showData(arr,oTimer,index,n){
    oTimer = setTimeout(function(){
      cxt.clearRect(0,0,W,H);
      //写入canvas中 
      saveData(arr[index++]); 
      if(index == n){
        clearTimeout(oTimer);
      }else{
        //迭代函数       
        showData(arr,oTimer,index,n);           
      }                     
    },60);      
  }   
  //重新混乱
  function showDataToRandom(dataArr,oTimer,n){
    oTimer = setTimeout(function fn(){
      cxt.clearRect(0,0,W,H);
      for(var i = 0; i < dataArr.length; i++){
        var temp = dataArr[i];
        var x0 = temp.x;
        var y0 = temp.y;
        var disX = temp.randomX - temp.x;
        var disY = temp.randomY - temp.y;
        cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1);             
      } 
      n--;
      if(n === 0){
        clearTimeout(oTimer);
      }else{
        showDataToRandom(dataArr,oTimer,n); 
      }             
    },60);  
  } 
  //混乱聚合
  function showRandomToData(dataArr,oTimer,n){
    oTimer = setTimeout(function(){
      cxt.clearRect(0,0,W,H);
      for(var i = 0; i < dataArr.length; i++){
        var temp = dataArr[i];
        var x0 = temp.randomX;
        var y0 = temp.randomY;
        var disX = temp.x - temp.randomX;
        var disY = temp.y - temp.randomY;
        cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1);  
      }   
      n--;
      if(n === 0){
        clearTimeout(oTimer);
      }else{
        showRandomToData(dataArr,oTimer,n); 
      }      
    },60);  
  }
  btn1.onclick = function(){
    btn1.arr = [];
    for(var i = 10; i > 1; i--){
      btn1.arr.push(setData(imageData,i,1));
    }
    showData(btn1.arr,btn1.oTimer,0,9);
  }
  btn2.onclick = function(){
    btn2.arr = [];
    for(var i = 10; i > 0; i--){
      btn2.arr.push(setData(imageData,2,i));
    }
    showData(btn2.arr,btn2.oTimer,0,10);
  }   
  btn3.onclick = function(){
    clearTimeout(btn3.oTimer);
    showRandomToData(dataArr,btn3.oTimer,10);
  }
  btn4.onclick = function(){
    clearTimeout(btn4.oTimer);
    showDataToRandom(dataArr,btn4.oTimer,10);
  }  
  //鼠标移动
  drawing1.onmousemove = function(e){
    e = e || event;
    var x = e.clientX - drawing1.getBoundingClientRect().left;
    var y = e.clientY - drawing1.getBoundingClientRect().top;
    cxt.beginPath();
    cxt.arc(x,y,10,0,Math.PI*2);
    for(var i = 0; i < dataArr.length; i++){
      var temp = dataArr[i];
      if(cxt.isPointInPath(temp.x,temp.y)){   
        cxt.fillStyle = 'red';
        cxt.fillRect(temp.x,temp.y,1,1);
      }        
    }
    cxt.fillStyle = 'black';   
  }    
  //鼠标点击
  drawing1.onmousedown = function(e){
    var r = 20;
    e = e || event;
    var x = e.clientX - drawing1.getBoundingClientRect().left;
    var y = e.clientY - drawing1.getBoundingClientRect().top;
    cxt.beginPath();
    cxt.arc(x,y,r,0,Math.PI*2);
    for(var i = 0; i < dataArr.length; i++){
      var temp = dataArr[i];
      if(cxt.isPointInPath(temp.x,temp.y)){  
        temp.mark = true;
        var angle = Math.atan2((temp.y - y),(temp.x - x));
        temp.endX =  x - r*Math.cos(angle);
        temp.endY =  y - r*Math.sin(angle);
        var disX = temp.x - temp.endX;
        var disY = temp.y - temp.endY;
        cxt.fillStyle = '#fff';
        cxt.fillRect(temp.x,temp.y,1,1);
        cxt.fillStyle = '#f00';
        cxt.fillRect(temp.endX,temp.endY,1,1);  
        cxt.fillStyle="#000";
        dataRecovery(10);
      }else{
        temp.mark = false;
      }     
    }
    var oTimer = null;
    function dataRecovery(n){
      clearTimeout(oTimer);
      oTimer = setTimeout(function(){
        cxt.clearRect(0,0,W,H);
        for(var i = 0; i < dataArr.length; i++){
          var temp = dataArr[i];
          if(temp.mark){
            var x0 = temp.endX;
            var y0 = temp.endY;
            var disX = temp.x - x0;
            var disY = temp.y - y0;
            cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1);  
          }else{
            cxt.fillRect(temp.x,temp.y,1,1);
          }
        }   
        dataRecovery(n-1);    
        if(n === 1){
          clearTimeout(oTimer);
        }      
      },17);
    } 
  }  
}
</script> 
</body>
</html>

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图
Copyright @ 2010-2019 美高梅手机版4858 版权所有