成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

使用CANVAS實(shí)現(xiàn)交互性圓形馬賽克效果

starsfun / 1007人閱讀

摘要:在中任意從本地選擇一張圖片,然后通過(guò)鼠標(biāo)移動(dòng)或者移動(dòng)端就能實(shí)現(xiàn)圓形分裂的效果。個(gè)人習(xí)慣在構(gòu)造函數(shù)最后加上方法,方法里做一些準(zhǔn)備工作,完成前的一些必要的事情。繪制第一個(gè),也是最大的一個(gè)圓形。

在看D3.js的時(shí)候,無(wú)意間看到了一個(gè)例子,覺(jué)得很有趣,像是會(huì)分裂的圓形馬賽克。看了下代碼,使用svg完成的,但是具體實(shí)現(xiàn)方式使得在手機(jī)端無(wú)法把玩,于是就自己實(shí)現(xiàn)了一個(gè)canvas版本的。代碼很簡(jiǎn)單,canvas初學(xué)者可以自己試試當(dāng)做練筆,還是挺有趣的一個(gè)效果。

Online Demo

online demo

在demo中任意從本地選擇一張圖片,然后通過(guò)鼠標(biāo)移動(dòng)或者移動(dòng)端touchmove就能實(shí)現(xiàn)圓形分裂的效果。

使用

如果覺(jué)得用得著,你可以在自己的項(xiàng)目中安裝使用這個(gè)效果。npm i circle-split -S

難點(diǎn)

說(shuō)是難點(diǎn),其實(shí)根本不難。一開始看到的時(shí)候會(huì)好奇大大小小的圓形的顏色是怎么計(jì)算的,計(jì)算該面積下的平均值?其實(shí)很簡(jiǎn)單,就是從繪制了圖片的canvas上獲取圓心坐標(biāo)在圖片對(duì)應(yīng)位置上的顏色值。這樣的算法在圓形半徑較大的時(shí)候,對(duì)被遮蓋的圖片區(qū)域顏色代表性其實(shí)不好,但是從整個(gè)分裂過(guò)程來(lái)看,這個(gè)取色方案的效果還不錯(cuò)。

關(guān)鍵技術(shù)點(diǎn)

canvas繪圖:CanvasRenderingContext2D.drawImage()

canvas繪制圓形:CanvasRenderingContext2D.arc()

canvas上取指定坐標(biāo)上的顏色值:CanvasRenderingContext2D.getImageData()

思路

將圖片繪制在一個(gè)offline(即不用掛在DOM樹上)的canvas上,為了在指定位置獲取顏色用

創(chuàng)建另一個(gè)canvas,用來(lái)繪制圓。兩個(gè)canvas尺寸保持一致(而且都是方形),方便無(wú)需坐標(biāo)轉(zhuǎn)換獲取顏色

繪制第一個(gè)圓形,以canvas中心為圓心,使用對(duì)應(yīng)offline canvas坐標(biāo)上的顏色填充

維持一個(gè)circles數(shù)組,代表所有的圓,每個(gè)元素有坐標(biāo)(x, y),半徑(r)和是否標(biāo)記分裂(readyToSplit)

需要一個(gè)渲染循環(huán)(rendering loop),不斷的找出被標(biāo)記需要分裂(readyToSplit)的圓,拿去做分裂繪制

事件處理:當(dāng)mousemove或者touchmove發(fā)生在圓上時(shí),該圓被標(biāo)記readyToSplit = true,后面的則有渲染循環(huán)去處理

測(cè)試驅(qū)動(dòng)

在我自己做這樣的編程時(shí),會(huì)以測(cè)試驅(qū)動(dòng)的方式開始代碼。因此會(huì)腦子里先寫下自己的類將被如何使用,怎么樣能夠簡(jiǎn)單易用。

我打算把這個(gè)效果封裝成一個(gè)類,它將在使用時(shí)被實(shí)例化。最終的效果肯定是要在DOM樹上顯示的,所以這里在實(shí)例化時(shí)肯定需要指定一個(gè)mount節(jié)點(diǎn),所有的事情在其內(nèi)部進(jìn)行。而且,按照通常的習(xí)慣,開放一些配置,使得使用者可以做一些簡(jiǎn)單的定制化。但是目前還沒(méi)有想好哪些內(nèi)部的配置拿出來(lái)比較合適,所以第二個(gè)參數(shù)options可以后面再考慮。

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

我希望能夠動(dòng)態(tài)的切換顯示的圖片內(nèi)容,所以想提供一個(gè)setImage的方法,它應(yīng)該能接受圖片路徑,或者Image元素對(duì)象。

cs.setImage(image);

OK,這就是目前我希望的實(shí)例化方式,和想要提供的接口。后面再具體實(shí)現(xiàn)過(guò)程中,可以再繼續(xù)添加或者修改。

試想內(nèi)部

結(jié)合前面談到的實(shí)現(xiàn)思路,考慮CircleSplit類里面該如果定義屬性和私有共有方法。

從構(gòu)造函數(shù)入手。個(gè)人習(xí)慣在構(gòu)造函數(shù)最后加上init方法,init方法里做一些準(zhǔn)備工作,完成setImage前的一些必要的事情。

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

CircleSplit.prototype._init = function () {
    this._createSourceCanvas(); // 創(chuàng)建源canvas,用來(lái)繪制圖片,作為offline canvas,提供坐標(biāo)顏色使用
    this._createTargetCanvas(); // 創(chuàng)建目標(biāo)canvas,用來(lái)繪制看到的大大小小的圓
    this._render(); // 開啟渲染循環(huán)
    this.bindEvent(); // 綁定事件,touchmove mousemove這些
}

這樣我們一下子多了好幾個(gè)函數(shù),而且目的都很明確,因此可以很容易的判斷需要那些實(shí)例屬性和該如何實(shí)現(xiàn)各自函數(shù)體。這里可能需要多注意一下_render(),思路中談到在這里應(yīng)該去繪制需要分裂的圓,那么大致應(yīng)該像下面這樣:

CircleSplit.prototype._render = function () {
    // 循環(huán)體
    this.circles.forEach(function (circle) {
        if (circle.readyToSplit) {
            this._splitCircle(circle);
            circle.readyToSplit = false;
        }
    }, this);
    
    // 下一個(gè)循環(huán)
    requestAnimationFrame(this._render.bind(this));
}

而什么時(shí)候設(shè)置circle.readyToSplit呢?就是在bindEvent()的事件處理函數(shù)里面。這里會(huì)通過(guò)_tagCircle()遍歷circles,找到能hit到事件坐標(biāo)的一個(gè)圓,將其標(biāo)記(tag)上readyToSplit。

從共有方法入手setImage之后,相當(dāng)于將整個(gè)CircleSplit中的狀態(tài)都重置了下,circles數(shù)組得重置,兩個(gè)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。繪制第一個(gè),也是最大的一個(gè)圓形。圓心為canvas中心,半徑為canvas的一半
}

_drawSourceImage()里面就是調(diào)用了CanvasRenderingContext2D.drawImage()進(jìn)行圖片繪制。這個(gè)API函數(shù)有3種傳參形式,我這里選擇了5參數(shù)的形式,使用了自己寫的簡(jiǎn)易的居中庫(kù)CenterIt,來(lái)解決圖片居中繪制問(wèn)題:無(wú)論圖片尺寸,都可以輕易的居中覆蓋填充(cover)或者居中包含(contain)填充。

這里的_drawCircle(x, y, r)應(yīng)該能重用,后面每次圓形分裂的時(shí)候都能調(diào)用。初步給它3個(gè)參數(shù),圓心坐標(biāo)和半徑。在其內(nèi)部應(yīng)該能夠自己去獲取坐標(biāo)對(duì)應(yīng)的顏色值。所以簡(jiǎn)單想象一下它的內(nèi)部:

CircleSplit.prototype._drawCircle = function (x, y, r) {
    ...
    context.fillStyle = this._getColor(x, y); // 獲取坐標(biāo)顏色
  context.beginPath();
  context.arc(x, y, r, 0, 2 * Math.PI);
  context.closePath();
  context.fill();
    ...
}

繪制圓時(shí)使用CanvasRenderingContext2D.arc()API,使用起來(lái)不算簡(jiǎn)單明了,每次還需要begin和close Path。相比而下,一些canvas的游戲庫(kù)或者圖形庫(kù),則簡(jiǎn)單直觀的多:

// 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;

因此,如果要做比較復(fù)雜的繪制操作,推薦找一個(gè)適合自己的canvas庫(kù),會(huì)使得工作變得容易的多。

關(guān)于_getColor()函數(shù),這里使用了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] + ")";
}

如下圖:

假設(shè)左上角起始點(diǎn)為(x, y),一個(gè)方格為一個(gè)像素,那么getImageData(x, y, 1, 1).data就會(huì)返回[255,0,0,255],代表Red=255,Alpha=255。如果getImageData(x, y, 2, 2).data就會(huì)返回[255,0,0,255, 255,0,0,255, 255,0,0,255, 255,0,0,255] 長(zhǎng)度為16的數(shù)組,每4個(gè)為一組代表一個(gè)像素上的rgba值。getImageData()就是一個(gè)能幫助我們對(duì)canvas進(jìn)行像素級(jí)別操作的API函數(shù)。

一些基于canvas的“刮刮卡”插件,也是getImageData()的應(yīng)用:在圖片上絕對(duì)定位一個(gè)灰色的canvas,代表刮刮卡蒙層;通過(guò)對(duì)手指觸摸的像素點(diǎn)的alpha值進(jìn)行修改來(lái)實(shí)現(xiàn)被“刮“開的效果。當(dāng)然這里的修改需要使用到配套的putImageData()函數(shù);同時(shí)對(duì)整個(gè)canvas像素中alpha值為0的像素點(diǎn)的百分比 進(jìn)行統(tǒng)計(jì),可以完成刮開了80%就展示全部圖片的效果。

實(shí)現(xiàn)

上面是大致的實(shí)現(xiàn)思路,和編碼的思想過(guò)程。為了表達(dá)出我自己在完成一個(gè)功能的時(shí)候,是如何從無(wú)到有,定義屬性,定義API的。只是自己的一點(diǎn)經(jīng)驗(yàn),希望有幫助。

如果你對(duì)這些知識(shí)不熟悉,卻也感興趣的話,可以參考該github項(xiàng)目代碼

問(wèn)題與優(yōu)化

github上的代碼與上面講的思路一致,但是會(huì)有些不一樣,主要是在功能實(shí)現(xiàn)之后,發(fā)現(xiàn)了一個(gè)需要優(yōu)化的地方。

渲染速度_render()渲染循環(huán)中,我們對(duì)所有的circles進(jìn)行遍歷。但是當(dāng)整副圖片分裂次數(shù)很徹底時(shí),會(huì)有上萬(wàn)個(gè)圓,會(huì)導(dǎo)致每個(gè)渲染循環(huán)里的計(jì)算時(shí)間過(guò)長(zhǎng),導(dǎo)致下一個(gè)渲染循環(huán)在理想的時(shí)間后才執(zhí)行,從而導(dǎo)致了卡頓的感覺(jué)。于是為了解決這個(gè)問(wèn)題,引入了renderingCircles數(shù)組,將被標(biāo)記的circle全部插入這個(gè)數(shù)組中,渲染循環(huán)中只關(guān)心這里的值,用額外的存儲(chǔ)空間換更短的計(jì)算時(shí)間。

顯示模糊 最先的實(shí)現(xiàn)中,兩個(gè)canvas得尺寸是根據(jù)mountNode決定的,canvas.width canvas.height被設(shè)為和mountNode一樣的維度值。于是在一些設(shè)備上顯示出明顯的邊緣鋸齒。這里的解決方案就是設(shè)置canvas的寬和高為兩倍于mountNode的寬高,然后通過(guò)style去設(shè)置canvas顯示成和mountNode一樣的尺寸。這里就是canvas的自身的寬高屬性和canvas style的寬高之前的區(qū)別的理解和應(yīng)用。

圖片跨域問(wèn)題 在canvas操作圖片時(shí),可能會(huì)碰到這樣的錯(cuò)誤信息:Unable to get image data from canvas because the canvas has been tainted by cross-origin data.

關(guān)于這個(gè)的官方解釋是:

在canvas上可以繪制沒(méi)有跨域許可的圖片資源(images without CORS approval),但是這樣做會(huì)“感染(taints)”的canvas,而在感染的(tainted)canvas上調(diào)用toBlog(),toDataURL()getImageData()會(huì)拋出上面的安全方面的錯(cuò)誤。

CircleSplit.setImage(imageUrl)時(shí),可能就會(huì)碰到這個(gè)問(wèn)題。
解決方案,首先需要圖片有跨域許可。這個(gè)需要在提供圖片服務(wù)的server上進(jìn)行配置。這里不多介紹,有跨域許可的圖片被加載時(shí),在控制臺(tái)上應(yīng)該能看到:(這里我使用的七牛的圖片)

其次,需要在加載圖片時(shí),設(shè)置crossOrigin屬性:

var image = new Image();
image.crossOrigin = "anonymous";
image.onload = function () {};
image.src = imageUrl;
應(yīng)用

其實(shí)個(gè)人很喜歡最后完成的交互效果(有點(diǎn)強(qiáng)迫癥,喜歡不斷的戳掉泡泡),于是將這個(gè)小效果做了一個(gè)簡(jiǎn)單的H5頁(yè)面,在年底這個(gè)時(shí)間點(diǎn)里,講述和回顧在2016年的大事件。你也可以來(lái)體驗(yàn)下:2016-recap

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

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/50320.html

相關(guān)文章

  • 使用CANVAS實(shí)現(xiàn)互性圓形賽克效果

    摘要:在中任意從本地選擇一張圖片,然后通過(guò)鼠標(biāo)移動(dòng)或者移動(dòng)端就能實(shí)現(xiàn)圓形分裂的效果。個(gè)人習(xí)慣在構(gòu)造函數(shù)最后加上方法,方法里做一些準(zhǔn)備工作,完成前的一些必要的事情。繪制第一個(gè),也是最大的一個(gè)圓形。 在看D3.js的時(shí)候,無(wú)意間看到了一個(gè)例子,覺(jué)得很有趣,像是會(huì)分裂的圓形馬賽克??戳讼麓a,使用svg完成的,但是具體實(shí)現(xiàn)方式使得在手機(jī)端無(wú)法把玩,于是就自己實(shí)現(xiàn)了一個(gè)canvas版本的。代碼很簡(jiǎn)單...

    Aklman 評(píng)論0 收藏0
  • 基于Canvas的動(dòng)畫基本原理與數(shù)理分析

    摘要:注以下所有代碼托管到動(dòng)畫的數(shù)理分析有了前面的基礎(chǔ)知識(shí),現(xiàn)在我們就會(huì)想如果我們能夠在每秒幀,內(nèi)渲染張圖像,并且每一張圖像的內(nèi)容發(fā)生微調(diào),那么在秒鐘整個(gè)畫面就會(huì)產(chǎn)生動(dòng)畫效果了。 什么是動(dòng)畫? 就像思考哲學(xué)問(wèn)題無(wú)法回避思維和存在的關(guān)系一樣,制作動(dòng)畫同樣無(wú)法逃避的問(wèn)題是動(dòng)畫的原理是什么?這里提一句題外話,任何原理的東西通常難以讓你短期拾掇成果,但在隱約的未來(lái)會(huì)起到難以置信的效果,不信就看接下來(lái)...

    genedna 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<