關於節日
聖誕節,元旦,看大家(情侶)在朋友圈裏發各種慶祝的或者祝福的話語,甚是感動,然後悄悄拉黑了。作爲單身狗,我們也有自己慶祝節日的方式,今天我們就來實現一些祝福的效果。
需要說明的是,所有的效果都是利用canvas
來實現的。
祝福話語
偷了朋友的圖,很基本的慶祝方式,展示不同的文字,一段時間切換一次,普普通通,但是對於低像素來說,是最好的方法了,也是慶祝節日用的最多的了,我們這裏做個效果多一點的版本
效果展示:
基本原理是這樣的:
- 在
canvas
中把字畫出來,漸變色效果,通過canvas
的相關API獲取imageData
,就是像素點信息,同rgba。 - 遍歷
imageData
,生成相關 dom。 - 設置定時,因爲渲染不同的文字效果,當然,有過渡效果。
過程對應的代碼:
-
在
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);
像素點過多會卡頓,所以這裏儘量用少的點去完成效果
- 獲取
imageData
,生成相關 dom
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 打印一下
console.log(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';
}
- 定時器比較簡單,就不寫了,具體可以看源碼。
注意的點,Chrome下有點卡頓,Safari和Firefox下沒有卡頓,原因未知。
預覽效果-本地Chrome下打開很卡,火狐、safari正常
聖誕樹
早先的時候是聖誕節的時候,看到各種用字符組成聖誕樹的形式,於是自己就去試了下,還是比較簡單的。
這段用的是項目裏的js代碼,不過一看就是不可執行的,因爲我是按照空格分割的。
需要注意的點是:
- 因爲是處理文件,所以我們需要藉助 node
- 怎樣處理圖片,生成相應的代碼
- 如何讓切割後的代碼仍然可以執行
對於上面的幾點,做以下分析:
關於第一點和第二點,和上面的例子一樣,我們還是需要 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);
}
工具的配置非常多,文檔看起來也是很麻煩,有個 npm 包,node-fluent-ffmpeg,用着也還可以,我剛開始用了,但是感覺功能不能滿足,而且使用這個包的前提是你全局安裝了ffmpeg
...
總結
這個我拖了比較久,有的東西有點記不清楚,可能有些東西表達的不好,說的不是很細,一些api
的說明我都省略了,這些MDN
上都有,就沒做過多說明,當然,你可以做些更有趣的事情,文檔,本來自己還想做些有趣的東西,但後面沒啥時間,就沒繼續做下去了,希望有興趣的朋友可以去嘗試一波,還是很有意思的。
就醬,感謝閱讀~