前端慶祝節日的方法

shiyangzhaoa發表於2019-01-21

關於節日

聖誕節,元旦,看大家(情侶)在朋友圈裡發各種慶祝的或者祝福的話語,甚是感動,然後悄悄拉黑了。作為單身狗,我們也有自己慶祝節日的方式,今天我們就來實現一些祝福的效果。

需要說明的是,所有的效果都是利用canvas來實現的。

祝福話語

跨年

偷了朋友的圖,很基本的慶祝方式,展示不同的文字,一段時間切換一次,普普通通,但是對於低畫素來說,是最好的方法了,也是慶祝節日用的最多的了,我們這裡做個效果多一點的版本效果展示:

效果

基本原理是這樣的:

  1. canvas中把字畫出來,漸變色效果,通過canvas的相關API獲取imageData,就是畫素點資訊,同rgba。
  2. 遍歷imageData,生成相關 dom。
  3. 設定定時,因為渲染不同的文字效果,當然,有過渡效果。

過程對應的程式碼:

  1. canvas裡寫字,且漸變效果:
// 畫素點的單位長度const rectWidth =  parseFloat(document.documentElement.style.getPropertyValue('--rect-width'));
const canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 20;
const ctx = canvas.getContext('2d');
ctx.font = '100 18px monospace';
ctx.textBaseline = 'top';
// 設定文字基線ctx.textAlign = 'center';
// 將區域內所有畫素點設定成透明ctx.clearRect(0, 0, canvas.width, canvas.height);
// 漸變效果const gradient = ctx.createLinearGradient(10, 0, canvas.width - 10, 0);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1 / 6, 'orange');
gradient.addColorStop(2 / 6, 'yellow');
gradient.addColorStop(3 / 6, 'green');
gradient.addColorStop(4 / 6, 'blue');
gradient.addColorStop(5 / 6, 'indigo');
gradient.addColorStop(1, 'violet');
ctx.fillStyle = gradient;
// y設定2,是因為火狐瀏覽器下效果有異常...ctx.fillText('這是測試', canvas.width / 2, 2);
// 插入document.body.appendChild(canvas);
複製程式碼
效果

畫素點過多會卡頓,所以這裡儘量用少的點去完成效果

  1. 獲取imageData,生成相關 dom
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 列印一下console.log(imageData);
複製程式碼
imageData

imageData包含三個屬性,data,width和height,data是一個一維陣列,[[0-255], [0-255], [0-255], [0-255]],長度是4的倍數,4個算一小組,相當於rgba,只不過透明度範圍也是0~255,width和height相當於長寬,畫素點數量 = (高 * 寬) * 4

{ 
let i = 2000;
const fragment = document.createDocumentFragment();
while (i-- >
0) {
fragment.appendChild(document.createElement('li'));

} ul.appendChild(fragment);

}let iLi = 0;
for (let column = 0;
column <
imageData.width;
column++) {
for (let row = 0;
row <
imageData.height;
row++) {
// 第幾個畫素點起始位置,肯定是4的倍數 const idx = ((row * imageData.width) + column) * 4;
if (imageData.data[idx + 3] >
0) {
const li = ul.children[iLi++];
li.style.opacity = '1';
// 觀察css你會發現,所有顯示的點初始位置都是在中心 li.style.transform = `translate( ${column * rectWidth
}px, ${row * rectWidth
}px) scale(1.5)`;
// 這裡 scale 完全是為了好看 li.style.background = `rgba(${imageData.data[idx]
},${imageData.data[idx + 1]
},${imageData.data[idx + 2]
},${imageData.data[idx + 3] / 255
})`;

}
}
}while (iLi <
2000) {
const li = ul.children[iLi++];
li.style.opacity = '0';

}複製程式碼
效果
  1. 定時器比較簡單,就不寫了,具體可以看原始碼。

注意的點,Chrome下有點卡頓,Safari和Firefox下沒有卡頓,原因未知。

預覽效果-本地Chrome下開啟很卡,火狐、safari正常

聖誕樹

早先的時候是聖誕節的時候,看到各種用字元組成聖誕樹的形式,於是自己就去試了下,還是比較簡單的。

聖誕樹

這段用的是專案裡的js程式碼,不過一看就是不可執行的,因為我是按照空格分割的。

需要注意的點是:

  1. 因為是處理檔案,所以我們需要藉助 node
  2. 怎樣處理圖片,生成相應的程式碼
  3. 如何讓切割後的程式碼仍然可以執行

對於上面的幾點,做以下分析:

關於第一點和第二點,和上面的例子一樣,我們還是需要 canvas,node 環境並沒有 canvas 這個 element,需要藉助第三方的庫node-canvas(npm)例子:

繪製

繪製好圖片,我們就能像上面一樣拿到需要的 ImageData,然後就是寫檔案,基本上是非常簡單了,寫的時候考慮到 canvas 的API比較多,用了 typescript,不影響閱讀,都9102年了,你可以不用,你也應該全域性裝以下typescript(畢竟如今typescript已經成了社交語言,“哎呦,你也在用typescript的啊,我也在用呢~”)

先寫個簡單版本,用text格式,展示基本圖形

const fs = require("fs");
const path = require('path');
const {
createCanvas, loadImage
} = require('canvas');
const canvas = createCanvas(80, 80)const ctx: CanvasRenderingContext2D = canvas.getContext('2d')async function transform(input: string, output: string) {
const image: ImageBitmap = await loadImage(input);
ctx.drawImage(image, 0, 0, 80, 80);
const imageData: ImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const {
width, height, data
} = imageData;
let outStr = '';
for (let col = 0;
col <
height;
col++) {
for (let row = 0;
row <
width;
row++) {
const index = ((col * height) + row) * 4;
const r = data[index];
const g = data[index + 1];
const b = data[index + 2];
const a = data[index + 3];
// “黑色”區間, 找的圖片不是完全黑色 if (r <
100 &
&
g <
100 &
&
b <
100 &
&
a === 255) {
outStr += '+';

} else {
outStr += ' ';

}
} outStr += '\n';

} console.log(outStr);
fs.writeFileSync(output, outStr);

}transform(path.join(__dirname, '../img/tree.jpg'), path.join(__dirname, '../outputs/demo2.txt'));
複製程式碼

效果:

聖誕樹

關於把js程式碼切割成可執行的樣子,這塊我想了很久,剛開始只是是想把js檔案按空格切割成陣列,給定一個初始的變數start,記錄到什麼位置,因為一些變數名是不能分割,但js一些語法特性不好處理,比如說

function test() { 
return function aa() {
}
}複製程式碼

function test() { 
return function aa() {
}
}複製程式碼

完全是兩個函式,後面在網上看了下,發現了芋頭大大很久以前寫過一篇類似的,地址,有興趣的小夥伴可以看看,這塊不做過多說明,實現還是有點麻煩的

會動的字元

上面說了字元和圖片,自然而然的,下面說的應該就是視訊了。視訊的話,也是非常簡單的,因為視訊是由連續的圖片組成的,也就是不斷變化的圖片,就是所謂的“幀”。也就是,如果我們能拿到視訊所有定格的圖片,就能作出相應的動畫效果。

需要把視訊“拆成”圖片,需要藉助第三方的工具,ffmpeg,功能比較強大,具體不做說明,需要安裝到全域性,利用brew,執行brew install ffmpeg就好了(大概,我好像是這樣裝的233),windows使用者下載要配置環境變數之類的,自己查一下吧。

// 主要程式碼const mvPath = path.join(__dirname, '../mv/bad-apple.flv');
const imgPath = path.join(__dirname, '../img');
const setTime = (t: number) =>
new Promise((resolve) =>
{
setTimeout(() =>
resolve(), t);

});
try {
void async function main() {
let img = fs.readdirSync(imgPath);
let len = img.length;
if (len <
= 1) {
await execSync(`cd ${imgPath
}
&
&
ffmpeg -i ${mvPath
}
-f image2 -vf fps=fps=30 bad-%d.png`
);
img = fs.readdirSync(imgPath);
len = img.length;

} let start = 1;
let count = len;
(async function inter(i: number) {
if (i <
count) {
await transform(path.join(__dirname, `../img/bad-${i
}
.png`
));
await setTime(33.33);
await inter(++i);

}
})(start);

}()
} catch (err) {
console.log(err);

}複製程式碼
bad-apple

工具的配置非常多,文件看起來也是很麻煩,有個 npm 包,node-fluent-ffmpeg,用著也還可以,我剛開始用了,但是感覺功能不能滿足,而且使用這個包的前提是你全域性安裝了ffmpeg

總結

GitHub原始碼

這個我拖了比較久,有的東西有點記不清楚,可能有些東西表達的不好,說的不是很細,一些api的說明我都省略了,這些MDN上都有,就沒做過多說明,文件,本來自己還想做些有趣的東西,但後面沒啥時間,就沒繼續做下去了,希望有興趣的朋友可以去嘗試一波,還是很有意思的。

就醬,感謝閱讀~

來源:https://juejin.im/post/5c2b766051882575f560553b

相關文章