使用CANVAS实现交互性圆形马赛克效果

在看D3.js的时候,无意间看到了一个例子,觉得很有趣,像是会分裂的圆形马赛克。看了下代码,使用svg完成的,但是具体实现方式使得在手机端无法把玩,于是就自己实现了一个canvas版本的。代码很简单,canvas初学者可以自己试试当做练笔,还是挺有趣的一个效果。

Online Demo

online demo

在demo中任意从本地选择一张图片,然后通过鼠标移动或者移动端touchmove就能实现圆形分裂的效果。

使用

如果觉得用得着,你可以在自己的项目中安装使用这个效果。npm i circle-split -S

难点

说是难点,其实根本不难。一开始看到的时候会好奇大大小小的圆形的颜色是怎么计算的,计算该面积下的平均值?其实很简单,就是从绘制了图片的canvas上获取圆心坐标在图片对应位置上的颜色值。这样的算法在圆形半径较大的时候,对被遮盖的图片区域颜色代表性其实不好,但是从整个分裂过程来看,这个取色方案的效果还不错。

关键技术点

思路

  1. 将图片绘制在一个offline(即不用挂在DOM树上)的canvas上,为了在指定位置获取颜色用
  2. 创建另一个canvas,用来绘制圆。两个canvas尺寸保持一致(而且都是方形),方便无需坐标转换获取颜色
  3. 绘制第一个圆形,以canvas中心为圆心,使用对应offline canvas坐标上的颜色填充
  4. 维持一个circles数组,代表所有的圆,每个元素有坐标(x, y),半径(r)和是否标记分裂(readyToSplit)
  5. 需要一个渲染循环(rendering loop),不断的找出被标记需要分裂(readyToSplit)的圆,拿去做分裂绘制
  6. 事件处理:当mousemove或者touchmove发生在圆上时,该圆被标记readyToSplit = true,后面的则有渲染循环去处理

测试驱动

在我自己做这样的编程时,会以测试驱动的方式开始代码。因此会脑子里先写下自己的类将被如何使用,怎么样能够简单易用。

我打算把这个效果封装成一个类,它将在使用时被实例化。最终的效果肯定是要在DOM树上显示的,所以这里在实例化时肯定需要指定一个mount节点,所有的事情在其内部进行。而且,按照通常的习惯,开放一些配置,使得使用者可以做一些简单的定制化。但是目前还没有想好哪些内部的配置拿出来比较合适,所以第二个参数options可以后面再考虑。

var cs = new CircleSplit(‘#mountNode‘, options);

我希望能够动态的切换显示的图片内容,所以想提供一个setImage的方法,它应该能接受图片路径,或者Image元素对象。

cs.setImage(image);

OK,这就是目前我希望的实例化方式,和想要提供的接口。后面再具体实现过程中,可以再继续添加或者修改。

试想内部

结合前面谈到的实现思路,考虑CircleSplit类里面该如果定义属性和私有共有方法。

从构造函数入手。个人习惯在构造函数最后加上init方法,init方法里做一些准备工作,完成setImage前的一些必要的事情。

function CircleSplit (el, options) {
        ...
        this._init();
}

CircleSplit.prototype._init = function () {
    this._createSourceCanvas(); // 创建源canvas,用来绘制图片,作为offline canvas,提供坐标颜色使用
    this._createTargetCanvas(); // 创建目标canvas,用来绘制看到的大大小小的圆
    this._render(); // 开启渲染循环
    this.bindEvent(); // 绑定事件,touchmove mousemove这些
}

这样我们一下子多了好几个函数,而且目的都很明确,因此可以很容易的判断需要那些实例属性和该如何实现各自函数体。这里可能需要多注意一下_render(),思路中谈到在这里应该去绘制需要分裂的圆,那么大致应该像下面这样:

CircleSplit.prototype._render = function () {
    // 循环体
    this.circles.forEach(function (circle) {
        if (circle.readyToSplit) {
            this._splitCircle(circle);
            circle.readyToSplit = false;
        }
    }, this);

    // 下一个循环
    requestAnimationFrame(this._render.bind(this));
}

而什么时候设置circle.readyToSplit呢?就是在bindEvent()的事件处理函数里面。这里会通过_tagCircle()遍历circles,找到能hit到事件坐标的一个圆,将其标记(tag)上readyToSplit。

从共有方法入手setImage之后,相当于将整个CircleSplit中的状态都重置了下,circles数组得重置,两个canvas得重置等。

CircleSplit.prototype.setImage = function (image) {
    this._resetCanvas(this.sourceCanvas); // clear source canvas
    this._drawSourceImage(image); // draw source canvas
    this._resetCanvas(this.targetCanvas); // clear target canvas
    this._drawCircle(x, y, r) // draw target canvas。绘制第一个,也是最大的一个圆形。圆心为canvas中心,半径为canvas的一半
}

_drawSourceImage()里面就是调用了CanvasRenderingContext2D.drawImage()进行图片绘制。这个API函数有3种传参形式,我这里选择了5参数的形式,使用了自己写的简易的居中库CenterIt,来解决图片居中绘制问题:无论图片尺寸,都可以轻易的居中覆盖填充(cover)或者居中包含(contain)填充。

这里的_drawCircle(x, y, r)应该能重用,后面每次圆形分裂的时候都能调用。初步给它3个参数,圆心坐标和半径。在其内部应该能够自己去获取坐标对应的颜色值。所以简单想象一下它的内部:

CircleSplit.prototype._drawCircle = function (x, y, r) {
    ...
    context.fillStyle = this._getColor(x, y); // 获取坐标颜色
  context.beginPath();
  context.arc(x, y, r, 0, 2 * Math.PI);
  context.closePath();
  context.fill();
    ...
}

绘制圆时使用CanvasRenderingContext2D.arc()API,使用起来不算简单明了,每次还需要begin和close Path。相比而下,一些canvas的游戏库或者图形库,则简单直观的多:

// create.js
var circle = new createjs.Shape();
circle.graphics.beginFill("DeepSkyBlue").drawCircle(0, 0, 50);

// two.js
var circle = two.makeCircle(72, 100, 50);
circle.fill = ‘#FF8000‘;
circle.stroke = ‘orangered‘;
circle.linewidth = 5;

因此,如果要做比较复杂的绘制操作,推荐找一个适合自己的canvas库,会使得工作变得容易的多。

关于_getColor()函数,这里使用了CanvasRenderingContext2D.getImageData()

CircleSplit.prototype._getColor = function (x, y) {
    ...
    var pixelData = this.sourceCanvas.getContext(‘2d‘).getImageData(parseInt(x), parseInt(y), 1, 1).data;
  return ‘rgb(‘ + pixelData[0] + ‘,‘ + pixelData[1] + ‘,‘ + pixelData[2] + ‘)‘;
}

如下图:

假设左上角起始点为(x, y),一个方格为一个像素,那么getImageData(x, y, 1, 1).data就会返回[255,0,0,255],代表Red=255,Alpha=255。如果getImageData(x, y, 2, 2).data就会返回[255,0,0,255, 255,0,0,255, 255,0,0,255, 255,0,0,255] 长度为16的数组,每4个为一组代表一个像素上的rgba值。getImageData()就是一个能帮助我们对canvas进行像素级别操作的API函数。

一些基于canvas的“刮刮卡”插件,也是getImageData()的应用:在图片上绝对定位一个灰色的canvas,代表刮刮卡蒙层;通过对手指触摸的像素点的alpha值进行修改来实现被“刮“开的效果。当然这里的修改需要使用到配套的putImageData()函数;同时对整个canvas像素中alpha值为0的像素点的百分比 进行统计,可以完成刮开了80%就展示全部图片的效果。

实现

上面是大致的实现思路,和编码的思想过程。为了表达出我自己在完成一个功能的时候,是如何从无到有,定义属性,定义API的。只是自己的一点经验,希望有帮助。

如果你对这些知识不熟悉,却也感兴趣的话,可以参考该github项目代码

问题与优化

github上的代码与上面讲的思路一致,但是会有些不一样,主要是在功能实现之后,发现了一个需要优化的地方。

渲染速度_render()渲染循环中,我们对所有的circles进行遍历。但是当整副图片分裂次数很彻底时,会有上万个圆,会导致每个渲染循环里的计算时间过长,导致下一个渲染循环在理想的时间后才执行,从而导致了卡顿的感觉。于是为了解决这个问题,引入了renderingCircles数组,将被标记的circle全部插入这个数组中,渲染循环中只关心这里的值,用额外的存储空间换更短的计算时间。

显示模糊 最先的实现中,两个canvas得尺寸是根据mountNode决定的,canvas.width canvas.height被设为和mountNode一样的维度值。于是在一些设备上显示出明显的边缘锯齿。这里的解决方案就是设置canvas的宽和高为两倍于mountNode的宽高,然后通过style去设置canvas显示成和mountNode一样的尺寸。这里就是canvas的自身的宽高属性和canvas style的宽高之前的区别的理解和应用。

图片跨域问题 在canvas操作图片时,可能会碰到这样的错误信息:Unable to get image data from canvas because the canvas has been tainted by cross-origin data.

关于这个的官方解释是:

在canvas上可以绘制没有跨域许可的图片资源(images without CORS approval),但是这样做会“感染(taints)”的canvas,而在感染的(tainted)canvas上调用toBlog()toDataURL()getImageData()会抛出上面的安全方面的错误。

CircleSplit.setImage(imageUrl)时,可能就会碰到这个问题。
解决方案,首先需要图片有跨域许可。这个需要在提供图片服务的server上进行配置。这里不多介绍,有跨域许可的图片被加载时,在控制台上应该能看到:(这里我使用的七牛的图片)

其次,需要在加载图片时,设置crossOrigin属性:

var image = new Image();
image.crossOrigin = ‘anonymous‘;
image.onload = function () {};
image.src = imageUrl;

应用

其实个人很喜欢最后完成的交互效果(有点强迫症,喜欢不断的戳掉泡泡),于是将这个小效果做了一个简单的H5页面,在年底这个时间点里,讲述和回顾在2016年的大事件。你也可以来体验下:2016-recap

原文地址:http://blog.jackyang.me/blog/...

本文转载于:猿2048?https://www.mk2048.com/blog/blog.php?id=hhbi2hcibab

原文地址:https://www.cnblogs.com/homehtml/p/12565494.html

时间: 2024-11-08 20:20:08

使用CANVAS实现交互性圆形马赛克效果的相关文章

用HTML5 Canvas 做擦除及扩散效果

2013年的时候曾经使用canvas实现了一个擦除效果的需求,即模拟用户在模糊的玻璃上擦除水雾看到清晰景色的交互效果.好在2012年的时候学习HTML5的时候研究过canvas了,所以在比较短的时间内实现了一个方案[下文方案一],后来继续探索之后进一步更新了这个方案[下文方案二],提高了交互的性能,也提升了用户体验.今年初的另一个项目,提出了一个比较类似的需求,不过不是擦除效果,需要在一张地图上动态显示雾霾驱散的效果,这个交互需求有个小难点,雾霾的边缘是模糊的,而不是常见的那种整齐的. 这里说明

基于HTML5 Canvas实现的图片马赛克模糊特效

效果请点击下面网址: http://hovertree.com/texiao/html5/1.htm 一.开门见山受美国肖像画家Chuck Close的启发,此脚本通过使用HTML5 canvas元素把图像转换成像素形式,这基本上是一个为canvas imageData功能的简单演示. 此脚本现存于GitHub上,您可以在那里下载到脚本和示例.在GitHub上查看像素化资源 二.选项此方法接受一个对象数组,每个对象都拥有一组选项.resolution : 渲染像素间的像素距离,必须的.shape

使用JavaScript和Canvas打造真实的雨滴效果

使用JavaScript和Canvas打造真实的雨滴效果 寸志 · 1 年前 我最近搞了一个有趣的项目——rainyday.js .我认为这个项目并不怎么样,而且,事实上这是我第一次尝试接触一些比弹窗更复杂的JavaScript.幸好,你们觉得它还有点意思.rainyday.js想创建一个轻量的JavaScript类库,利用HTML5的canvas,来实现雨滴在玻璃上滑落的效果.很简单,不过有时候还是很有挑战的,尤其是在我们既要尽力避免动画区别于通常JavaScript的动画,又要保证动画流畅运

HTML5在canvas中绘制复杂形状附效果截图

HTML5在canvas中绘制复杂形状附效果截图 一.绘制复杂形状或路径 在简单的矩形不能满足需求的情况下,绘图环境提供了如下方法来绘制复杂的形状或路径. beginPath() : 开始绘制一个新路径. closePath() : 通过绘制一条当前点到路径起点的线段来闭合形状. fill() , stroke() : 填充形状或绘制空心形状. moveTo() : 将当前点移动到点(x,y). lineTo() : 从当前点绘制一条直线到点(x,y). arc(x,y,r,sAngle,eAn

经典!HTML5 Canvas 模拟可撕裂布料效果

这是一个模拟可撕裂布料效果的 HTML5 Canvas 应用演示,效果逼真.你会看到,借助 Canvas 的强大绘图和动画功能,只需很少的代码就能实现让您屏息凝神的效果. 温馨提示:为保证最佳的效果,请在 IE10+.Chrome.Firefox 和 Safari 等现代浏览器中浏览. 源码下载     效果演示 您可能感兴趣的相关文章 Web 开发中很实用的10个效果[源码下载] 精心挑选的优秀jQuery Ajax分页插件和教程 12个让人惊叹的的创意的 404 错误页面设计 让网站动起来!

iOS 图片实现马赛克效果

/** *实现马赛克效果 */ // 导出CIImage图片 CIImage *ciImage = [[CIImage alloc] initWithImage:[UIImageimageNamed:@"De.png"]]; // 1.创建Filter滤镜 CIFilter *filter = [CIFilter filterWithName:@"CIPixellate"]; [filter setValue:ciImage forKey:kCIInputImage

HTML5 Canvas实现的图片马赛克模糊特效

要想让图片像素化,首先调用如下脚本:<script type="text/javascript" src="http://hovertree.com/texiao/html5/1/js/close-pixelate.js"></script>然后调用方法closePixelate,更具体的是:图片dom.closePixelate(选项参数) 此脚本可以应用于各类图片.根据HTML5规范,浏览器禁止任何外部托管图片上使用getImageDat

canvas画简单圆形动画

HTML: 1 <html> 2 <head> 3 <title>canvas画圆</title> 4 <meta http-equiv="content-type" content="text/html" charset="utf-8"> 5 <link rel="stylesheet" href="circle.css"/> 6 &

结合 CSS3 &amp; Canvas 模拟人行走的效果

HTML5 和 CSS3 技术给 Web 带来了新的利器,点燃了 Web 开发人员的激情.所谓只有想不到,没有做不到,的确如此.下面给大家分享一个结合 CSS3 & Canvas 模拟人行走的动画效果. 温馨提示:为保证最佳的效果,请在 IE10+.Chrome.Firefox 和 Safari 等现代浏览器中浏览. 插件下载     效果演示 您可能感兴趣的相关文章 Web 开发中很实用的10个效果[源码下载] 精心挑选的优秀jQuery Ajax分页插件和教程 12个让人惊叹的的创意的 40