前端庆祝节日的方法

关于节日

圣诞节,元旦,看你们(情侣)在朋友圈里发各类庆祝的或者祝福的话语,甚是感动,而后悄悄拉黑了。做为单身狗,咱们也有本身庆祝节日的方式,今天咱们就来实现一些祝福的效果。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上都有,就没作过多说明,文档,原本本身还想作些有趣的东西,但后面没啥时间,就没继续作下去了,但愿有兴趣的朋友能够去尝试一波,仍是颇有意思的。

就酱,感谢阅读~

相关文章
相关标签/搜索