前端慶祝節日的方法

關於節日

聖誕節,元旦,看你們(情侶)在朋友圈裏發各類慶祝的或者祝福的話語,甚是感動,而後悄悄拉黑了。做爲單身狗,咱們也有本身慶祝節日的方式,今天咱們就來實現一些祝福的效果。css

須要說明的是,全部的效果都是利用canvas來實現的。html

祝福話語

跨年

偷了朋友的圖,很基本的慶祝方式,展現不一樣的文字,一段時間切換一次,普普統統,可是對於低像素來講,是最好的方法了,也是慶祝節日用的最多的了,咱們這裏作個效果多一點的版本 效果展現: node

效果

基本原理是這樣的:git

  1. canvas中把字畫出來,漸變色效果,經過canvas的相關API獲取imageData,就是像素點信息,同rgba。
  2. 遍歷imageData,生成相關 dom。
  3. 設置定時,由於渲染不一樣的文字效果,固然,有過渡效果。

過程對應的代碼:github

  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下沒有卡頓,緣由未知。typescript

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

聖誕樹

早先的時候是聖誕節的時候,看到各類用字符組成聖誕樹的形式,因而本身就去試了下,仍是比較簡單的。canvas

聖誕樹

這段用的是項目裏的js代碼,不過一看就是不可執行的,由於我是按照空格分割的。windows

須要注意的點是:api

  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上都有,就沒作過多說明,文檔,原本本身還想作些有趣的東西,但後面沒啥時間,就沒繼續作下去了,但願有興趣的朋友能夠去嘗試一波,仍是頗有意思的。

就醬,感謝閱讀~

相關文章
相關標籤/搜索