摘要:現(xiàn)在就一起來做一場流星雨,用程序員的野路子浪漫一下。要畫一場流星雨,首先,自然我們要會(huì)畫一顆流星。畫一顆流星是的,的卻是沒這個(gè),但是不代表我們畫不出來。而我們每一幀要保留的就是,上一幀透明度的流星,覆蓋畫布黑色矩形我們不能顯示。
開始
妹子都喜歡流星,如果她說不喜歡,那她一定是一個(gè)假妹子。
現(xiàn)在就一起來做一場流星雨,用程序員的野路子浪漫一下。
要畫一場流星雨,首先,自然我們要會(huì)畫一顆流星。
玩過 canvas 的同學(xué),你畫圓畫方畫線條這么 6,如果說叫你畫下面這個(gè)玩意兒,你會(huì)不會(huì)覺得你用的是假 canvas?canvas 沒有畫一個(gè)帶尾巴玩意兒的 api 啊。
畫一顆流星是的,的卻是沒這個(gè) api,但是不代表我們畫不出來。流星就是一個(gè)小石頭,然后因?yàn)樗俣冗^快產(chǎn)生大量的熱量帶動(dòng)周圍的空氣發(fā)光發(fā)熱,所以經(jīng)飛過的地方看起來就像是流星的尾巴,我們先研究一下流星這個(gè)圖像,整個(gè)流星處于他自己的運(yùn)動(dòng)軌跡之中,當(dāng)前的位置最亮,輪廓最清晰,而之前劃過的地方離當(dāng)前位置軌跡距離越遠(yuǎn)就越暗淡越模糊。
上面的分析結(jié)果很關(guān)鍵, canvas 上是每一幀就重繪一次,每一幀之間的時(shí)間間隔很短。流星經(jīng)過的地方會(huì)越來越模糊最后消失不見,那有沒有可以讓畫布畫的圖像每過一幀就變模糊一點(diǎn)而不是全部清除的辦法?如果可以這樣,就可以把每一幀用線段畫一小段流星的運(yùn)動(dòng)軌跡,最后畫出流星的效果。
騙紙!你也許會(huì)說,這那里像流星了???
別急,讓我多畫幾段給你看看。
什么? 還是不像? 我們把它畫小點(diǎn),這下總該像了把?
上面幾幅圖我是在 ps 上模擬的,本質(zhì)上 ps 也是在畫布上繪畫,我們馬上在 canvas 上試試。
那,直接代碼實(shí)現(xiàn)一下。
// 坐標(biāo) class Crood { constructor(x=0, y=0) { this.x = x; this.y = y; } setCrood(x, y) { this.x = x; this.y = y; } copy() { return new Crood(this.x, this.y); } } // 流星 class ShootingStar { constructor(init=new Crood, final=new Crood, size=3, speed=200, onDistory=null) { this.init = init; // 初始位置 this.final = final; // 最終位置 this.size = size; // 大小 this.speed = speed; // 速度:像素/s // 飛行總時(shí)間 this.dur = Math.sqrt(Math.pow(this.final.x-this.init.x, 2) + Math.pow(this.final.y-this.init.y, 2)) * 1000 / this.speed; this.pass = 0; // 已過去的時(shí)間 this.prev = this.init.copy(); // 上一幀位置 this.now = this.init.copy(); // 當(dāng)前位置 this.onDistory = onDistory; } draw(ctx, delta) { this.pass += delta; this.pass = Math.min(this.pass, this.dur); let percent = this.pass / this.dur; this.now.setCrood( this.init.x + (this.final.x - this.init.x) * percent, this.init.y + (this.final.y - this.init.y) * percent ); // canvas ctx.strokeStyle = "#fff"; ctx.lineCap = "round"; ctx.lineWidth = this.size; ctx.beginPath(); ctx.moveTo(this.now.x, this.now.y); ctx.lineTo(this.prev.x, this.prev.y); ctx.stroke(); this.prev.setCrood(this.now.x, this.now.y); if (this.pass === this.dur) { this.distory(); } } distory() { this.onDistory && this.onDistory(); } } // effet let cvs = document.querySelector("canvas"); let ctx = cvs.getContext("2d"); let T; let shootingStar = new ShootingStar( new Crood(100, 100), new Crood(400, 400), 3, 200, ()=>{cancelAnimationFrame(T)} ); let tick = (function() { let now = (new Date()).getTime(); let last = now; let delta; return function() { delta = now - last; delta = delta > 500 ? 30 : (delta < 16? 16 : delta); last = now; // console.log(delta); T = requestAnimationFrame(tick); ctx.save(); ctx.fillStyle = "rgba(0,0,0,0.2)"; // 每一幀用 “半透明” 的背景色清除畫布 ctx.fillRect(0, 0, cvs.width, cvs.height); ctx.restore(); shootingStar.draw(ctx, delta); } })(); tick();效果:一顆流星
sogoyi 快看,一顆活潑不做作的流星!!! 是不是感覺動(dòng)起來更加逼真一點(diǎn)?
流星雨我們再加一個(gè)流星雨 MeteorShower 類,生成多一些隨機(jī)位置的流星,做出流星雨。
// 坐標(biāo) class Crood { constructor(x=0, y=0) { this.x = x; this.y = y; } setCrood(x, y) { this.x = x; this.y = y; } copy() { return new Crood(this.x, this.y); } } // 流星 class ShootingStar { constructor(init=new Crood, final=new Crood, size=3, speed=200, onDistory=null) { this.init = init; // 初始位置 this.final = final; // 最終位置 this.size = size; // 大小 this.speed = speed; // 速度:像素/s // 飛行總時(shí)間 this.dur = Math.sqrt(Math.pow(this.final.x-this.init.x, 2) + Math.pow(this.final.y-this.init.y, 2)) * 1000 / this.speed; this.pass = 0; // 已過去的時(shí)間 this.prev = this.init.copy(); // 上一幀位置 this.now = this.init.copy(); // 當(dāng)前位置 this.onDistory = onDistory; } draw(ctx, delta) { this.pass += delta; this.pass = Math.min(this.pass, this.dur); let percent = this.pass / this.dur; this.now.setCrood( this.init.x + (this.final.x - this.init.x) * percent, this.init.y + (this.final.y - this.init.y) * percent ); // canvas ctx.strokeStyle = "#fff"; ctx.lineCap = "round"; ctx.lineWidth = this.size; ctx.beginPath(); ctx.moveTo(this.now.x, this.now.y); ctx.lineTo(this.prev.x, this.prev.y); ctx.stroke(); this.prev.setCrood(this.now.x, this.now.y); if (this.pass === this.dur) { this.distory(); } } distory() { this.onDistory && this.onDistory(); } } class MeteorShower { constructor(cvs, ctx) { this.cvs = cvs; this.ctx = ctx; this.stars = []; this.T; this.stop = false; this.playing = false; } createStar() { let angle = Math.PI / 3; let distance = Math.random() * 400; let init = new Crood(Math.random() * this.cvs.width|0, Math.random() * 100|0); let final = new Crood(init.x + distance * Math.cos(angle), init.y + distance * Math.sin(angle)); let size = Math.random() * 2; let speed = Math.random() * 400 + 100; let star = new ShootingStar( init, final, size, speed, ()=>{this.remove(star)} ); return star; } remove(star) { this.stars = this.stars.filter((s)=>{ return s !== star}); } update(delta) { if (!this.stop && this.stars.length < 20) { this.stars.push(this.createStar()); } this.stars.forEach((star)=>{ star.draw(this.ctx, delta); }); } tick() { if (this.playing) return; this.playing = true; let now = (new Date()).getTime(); let last = now; let delta; let _tick = ()=>{ if (this.stop && this.stars.length === 0) { cancelAnimationFrame(this.T); this.playing = false; return; } delta = now - last; delta = delta > 500 ? 30 : (delta < 16? 16 : delta); last = now; // console.log(delta); this.T = requestAnimationFrame(_tick); ctx.save(); ctx.fillStyle = "rgba(0,0,0,0.2)"; // 每一幀用 “半透明” 的背景色清除畫布 ctx.fillRect(0, 0, cvs.width, cvs.height); ctx.restore(); this.update(delta); } _tick(); } start() { this.stop = false; this.tick(); } stop() { this.stop = true; } } // effet let cvs = document.querySelector("canvas"); let ctx = cvs.getContext("2d"); let meteorShower = new MeteorShower(cvs, ctx); meteorShower.start();效果:流星雨 透明背景
先不急著激動(dòng),這個(gè)流星雨有點(diǎn)單調(diào),可以看到上面的代碼中,每一幀,我們用了透明度為 0.2 的黑色刷了一遍畫布,背景漆黑一片,如果說我們的需求是透明背景呢?
比如,我們要用這個(gè)夜景圖片做背景,然后在上面加上我們的流星,我們每一幀刷一層背景的小伎倆就用不了啦。因?yàn)槲覀円WC除開流星之外的部分,應(yīng)該是透明的。
這里就要用到一個(gè)冷門的屬性了,globalCompositeOperation,全局組合操作? 原諒我放蕩不羈的翻譯。
這個(gè)屬性其實(shí)就是用來定義后繪制的圖形與先繪制的圖形之間的組合顯示效果的。
他可以設(shè)置這些值
這些屬性說明沒必要仔細(xì)看,更不用記下來,直接看 api 示例 運(yùn)行效果就很清楚了。示例里,先繪制的是填充正方形,后繪制的是填充圓形。
是不是豁然開朗,一目了然?
對于我們來說,原圖像是每一幀畫完的所有流星,目標(biāo)圖像是畫完流星之后半透明覆蓋畫布的黑色矩形。而我們每一幀要保留的就是,上一幀 0.8 透明度的流星,覆蓋畫布黑色矩形我們不能顯示。
注意這里的 destination-out 和 destination-in,示例中這兩個(gè)屬性最終都只有部分源圖像保留了下來,符合我們只要保留流星的需求。我覺得 w3cschool 上描述的不是很正確,我用我自己的理解概括一下。
destination-in :只保留了源圖像(矩形)和目標(biāo)圖像(圓)交集區(qū)域的源圖像
destination-out:只保留了源圖像(矩形)減去目標(biāo)圖像(圓)之后區(qū)域的源圖像
上述示例目標(biāo)圖像的透明度是 1,源圖像被減去的部分是完全不見了。而我們想要的是他可以按照目標(biāo)透明度進(jìn)行部分擦除。改一下示例里的代碼看看是否支持半透明的計(jì)算。
看來這個(gè)屬性支持半透明的計(jì)算。源圖像和目標(biāo)圖像交疊的部分以半透明的形式保留了下來。也就是說如果我們要保留 0.8 透明度的流星,可以這樣設(shè)置 globalCompositeOperation
ctx.fillStyle = "rgba(0,0,0,0.8)" globalCompositeOperation = "destination-in"; ctx.fillRect(0, 0, cvs.width, cvs.height); // 或者 ctx.fillStyle = "rgba(0,0,0,0.2)" globalCompositeOperation = "destination-out"; ctx.fillRect(0, 0, cvs.width, cvs.height);最終效果
加上 globalCompositeOperation 之后的效果既最終效果:
github: https://github.com/gnauhca/dailyeffecttest/tree/master/b-meteorshower
快約上你的妹子看流星雨吧。
...
什么? 你沒有妹子?
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/86932.html
摘要:今天是中秋節(jié),于是突發(fā)奇想,欸不如用來畫一畫月亮吧。徑向漸變這是月亮的類,主要用到了里的徑向漸變效果。然后整體傾角度,并且填充時(shí)用上一個(gè)徑向漸變,就可以相當(dāng)完美的達(dá)到流行尾巴那樣漸行漸遠(yuǎn)漸模糊的樣子。 今天是中秋節(jié),于是突發(fā)奇想,欸不如用canvas來畫一畫月亮吧。 于是一副用canvas畫出的星空就這樣誕生了。 Demo 在這里我用了ES6語法,星星,月亮和流星都單獨(dú)寫成了一個(gè)mod...
摘要:啦啦啦,首先說下需求,產(chǎn)品想讓用戶在我們內(nèi),分享一張圖片到微信等平臺。圖片中包含用戶的姓名頭像和帶著自己信息的二維碼。然后,如何生成這張海報(bào)呢首先我們老大告訴我有一個(gè)插件叫其作用就是可以將節(jié)點(diǎn)轉(zhuǎn)化成圖片,是個(gè)不錯(cuò)的東西。 啦啦啦,首先說下需求,產(chǎn)品想讓用戶在我們app內(nèi),分享一張圖片到微信、qq等平臺。圖片中包含用戶的姓名、頭像、和帶著自己信息的二維碼。然后,如何生成這張海報(bào)呢~~~首...
摘要:啦啦啦,首先說下需求,產(chǎn)品想讓用戶在我們內(nèi),分享一張圖片到微信等平臺。圖片中包含用戶的姓名頭像和帶著自己信息的二維碼。然后,如何生成這張海報(bào)呢首先我們老大告訴我有一個(gè)插件叫其作用就是可以將節(jié)點(diǎn)轉(zhuǎn)化成圖片,是個(gè)不錯(cuò)的東西。 啦啦啦,首先說下需求,產(chǎn)品想讓用戶在我們app內(nèi),分享一張圖片到微信、qq等平臺。圖片中包含用戶的姓名、頭像、和帶著自己信息的二維碼。然后,如何生成這張海報(bào)呢~~~首...
摘要:啦啦啦,首先說下需求,產(chǎn)品想讓用戶在我們內(nèi),分享一張圖片到微信等平臺。圖片中包含用戶的姓名頭像和帶著自己信息的二維碼。然后,如何生成這張海報(bào)呢首先我們老大告訴我有一個(gè)插件叫其作用就是可以將節(jié)點(diǎn)轉(zhuǎn)化成圖片,是個(gè)不錯(cuò)的東西。 啦啦啦,首先說下需求,產(chǎn)品想讓用戶在我們app內(nèi),分享一張圖片到微信、qq等平臺。圖片中包含用戶的姓名、頭像、和帶著自己信息的二維碼。然后,如何生成這張海報(bào)呢~~~首...
閱讀 984·2021-11-24 09:39
閱讀 2184·2021-11-16 11:54
閱讀 2076·2021-11-11 17:22
閱讀 2372·2021-09-30 09:55
閱讀 3590·2021-08-12 13:22
閱讀 1625·2019-08-30 15:44
閱讀 1168·2019-08-29 12:12
閱讀 3262·2019-08-27 10:58