如何利用node.js开发一个生成逐帧动画的小工具


Posted in Javascript onDecember 01, 2019

前言

在实际工作中我们已经下下来不下于一万个npm包了,像我们熟悉的 vue-cli,react-native-cli 等,只需要输入简单的命令 vue init webpack project,即可快速帮我们生成一个初始项目。在实际开发项目中,我们也可以定制一个属于自己的npm包,来提高自己的工作效率。

为什么要开发一个工具包?

  • 减少重复性的工作,不再需要复制其他项目再删除无关代码,或者从零创建一个项目和文件。
  • 根据交互动态生成项目结构和所需要的文件等。
  • 减少人工检查的成本。
  • 提高工作效率,解放生产力。

这次以帧动画工具为例,来一步一步解析如何开发一个npm包。

开始前的准备

以我们这次为例。由于目前在做一些活动页相关的工作,其中动画部分全都采用CSS3中的animation来完成,但是这样每次开发都要计算百分比,手动判断动画的一些属性值,十分耗时又很容易出错,就想能不能写个脚本,直接一行命令就可以搞定了呢?!答案当然是肯定的。

理清思路

我们要做一个可以通过读取图片就可以自动生成包含CSS animation的HTML页面,以后需要生成相应的CSS片段,直接执行命令就可以了。

初始化

既然是npm包,那我们就需要在npmjs上注册一个账号,注册完成之后回到本地新建一个文件目录fbf,进入fbf目录下执行npm init -y。

{
 "name": "fbf",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
  "test": "echo \"Error: no test specified\" && exit 1"
 },
 "bin": {
  "test": "index.js"
 },
 "keywords": [],
 "author": "",
 "license": "ISC"
}

这样,你的package.json就建好了。

依赖的库

来看看会用到哪些库。

  • commander.js,可以自动的解析命令和参数,用于处理用户输入的命令。
  • chalk,可以给终端的字体加上颜色。
  • create-html,创建HTML模版,用于生成HTML。
  • image-size,获取图片大小。
npm install commander chalk create-html image-size -S

命令行操作

node.js 内置了对命令行操作的支持,在 package.json 中的 bin 字段可以定义命令名和关联的执行文件。所以现在 package.json 中加上 bin 的内容:

{
 "name": "fbf",
 "version": "1.0.0",
 "description": "",
 "bin": {
 "fbf": "index.js"
 },
 ...
}

然后在 index.js 中来定义 start 命令:

#!/usr/bin/env node
const program = require('commander');
 
program.version('1.0.0', '-v, --version')
 .command('start <name>')
 .action((name) => {
 console.log(name);
 });
program.parse(process.argv);

调用 version('1.0.0', '-v, --version') 会将 -v 和 --version 添加到命令中,可以通过这些选项打印出版本号。

调用 command('start <name>') 定义 start 命令,name 则是必传的参数,为文件名。

action() 则是执行 start 命令会发生的行为,要生成项目的过程就是在这里面执行的,这里暂时只打印出 name。

其实到这里,已经可以执行 start 命令了。我们来测试一下,在 fbf 的同级目录下执行:

node ./test/index.js start HelloWorld

可以看到命令行工具也打印出了 HelloWorld,那么很清楚, action((name) => {}) 这里的参数 name,就是我们执行 start 命令时输入的项目名称。

命令已经完成,接下来就要针对图片的操作了。

获取图片信息

这里我们默认根据第一张图片的尺寸信息作为外层DIV的默认尺寸。

#!/usr/bin/env node
 const program = require('commander');
 const fs = require('fs');
 const path = require('path');
 const createHTML = require('create-html');
 const sizeOf = require('image-size');
 const chalk = require('chalk');
 
 program.version('1.0.0', '-v, --version')
 .command('start <dir>')
 .action((dir) => {
 
 //获取图片路径
 const imgPath = path.resolve(dir)
 
 let imgSize = null;
 fs.readdir(imgPath, (err, file) => {
  imgSize = sizeOf(dir + '/' +file[0]); 
  //取第一个图片的尺寸作为框尺寸
  let cssString = `
  .fbf-animation{
   width: ${imgSize.width}px;
   height: ${imgSize.height}px;
   margin:auto;
   background-image: url(./${dir}/${file[0]});
   background-size: ${imgSize.width}px ${imgSize.height}px;
   background-repeat: no-repeat;
   animation-name: keyframes-img;
   animation-duration: 0.36s;
   animation-delay: 0s;
   animation-iteration-count: infinite;
   animation-fill-mode: forwards;
   animation-timing-function: steps(1);
  }
 `
 })
 })

生成CSS代码

然后根据图片数量生成相应的keyframes代码

function toCss(dir, fileList){
 let _css = '';
 let start = 0;
 const per = Math.floor(100/fileList.length);
 fileList.map((path, i) => {
  if(i === fileList.length - 1){
  _css += `
   ${start + i*per}%, 100% {
   background:url(./${dir}/${path}) center center no-repeat;
   background-size:100% auto;
   }
  `
  }else{
  _css += `
   ${start + i*per}% {
   background:url(./${dir}/${path}) center center no-repeat;
   background-size:100% auto;
   }
  `
  }
 })
 
 return _css;
 }
 
 let frameCss = toCss(dir, newFile)
 
 //取第一个图片的尺寸作为框尺寸
 let cssString = `
 .fbf-animation{
  width: ${imgSize.width}px;
  height: ${imgSize.height}px;
  margin:auto;
  background-image: url(./${dir}/${file[0]});
  background-size: ${imgSize.width}px ${imgSize.height}px;
  background-repeat: no-repeat;
  animation-name: keyframes-img;
  animation-duration: 0.36s;
  animation-delay: 0s;
  animation-iteration-count: infinite;
  animation-fill-mode: forwards;
  animation-timing-function: steps(1);
 }
 @keyframes keyframes-img {
  ${frameCss}
 }

生成html文件

最后我们把生成的CSS放到HTML里。

//生成html
let html = createHTML({
 title: '逐帧动画',
 scriptAsync: true,
 lang: 'en',
 dir: 'rtl',
 head: '<meta name="description" content="example">',
 body: '<div class="fbf-animation"></div>' + css,
 favicon: 'favicon.png'
})
fs.writeFile('fbf.html', html, function (err) {
 if (err) console.log(err)
})

视觉美化

通过 chalk 来为打印信息加上样式,比如成功信息为绿色,失败信息为红色,这样子会让用户更加容易分辨,同时也让终端的显示更加的好看。

const chalk = require('chalk'); console.log(chalk.green('生成代码成功!')); console.log(chalk.red('生成代码失败'));

完整示例

#!/usr/bin/env node
const program = require('commander');
const fs = require('fs');
const path = require('path');
const createHTML = require('create-html');
const sizeOf = require('image-size');
const chalk = require('chalk');
 
//排序
const sortByFileName = files => {
 const reg = /[0-9]+/g;
 return files.sort((a, b) => {
 let imga = (a.match(reg) || []).slice(-1),
  imgb = (b.match(reg) || []).slice(-1)
 return imga - imgb
 });
}
 
//删除.DS_Store
function deleteDS(file) {
 file.map((v, i) => {
 if(v === '.DS_Store'){
  fs.unlink('img/.DS_Store', err => {})
 }
 })
}
 
// 生成keyframe
function toCss(dir, fileList){
 let _css = '';
 let start = 0;
 const per = Math.floor(100/fileList.length);
 fileList.map((path, i) => {
 if(i === fileList.length - 1){
  _css += `
  ${start + i*per}%, 100% {
   background:url(./${dir}/${path}) center center no-repeat;
   background-size:100% auto;
  }
  `
 }else{
  _css += `
  ${start + i*per}% {
   background:url(./${dir}/${path}) center center no-repeat;
   background-size:100% auto;
  }
  `
 }
 })
 console.log(chalk.green('css successed!'))
 return _css;
}
 
program.version('1.0.0', '-v, --version')
 .command('start <dir>')
 .action((dir) => {
 
 const imgPath = path.resolve(dir)
 
 let imgSize = null;
 fs.readdir(imgPath, (err, file) => {
  const newFile = sortByFileName(file)
  deleteDS(newFile)
  imgSize = sizeOf(dir + '/' +file[0]);
  let frameCss = toCss(dir, newFile)
 
  //取第一个图片的尺寸作为框尺寸
  let cssString = `
  .fbf-animation{
  width: ${imgSize.width}px;
  height: ${imgSize.height}px;
  margin:auto;
  background-image: url(./${dir}/${file[0]});
  background-size: ${imgSize.width}px ${imgSize.height}px;
  background-repeat: no-repeat;
  animation-name: keyframes-img;
  animation-duration: 0.36s;
  animation-delay: 0s;
  animation-iteration-count: infinite;
  animation-fill-mode: forwards;
  animation-timing-function: steps(1);
  }
  @keyframes keyframes-img {
  ${frameCss}
  }
 `
  let css = `
  <style>${cssString}</style>
 `
  //生成html
  let html = createHTML({
  title: '逐帧动画',
  scriptAsync: true,
  lang: 'en',
  dir: 'rtl',
  head: '<meta name="description" content="example">',
  body: '<div class="fbf-animation"></div>' + css,
  favicon: 'favicon.png'
  })
  fs.writeFile('fbf.html', html, function (err) {
  console.log(chalk.green('html successed!'))
  if (err) console.log(err)
  })
 })
 });
program.parse(process.argv);

代码一共100行左右,可以说非常简单明了,有兴趣的同学可以试试。

最后

完成之后,使用npm publish fbf就可以把脚手架发布到 npm 上面,通过 -g 进行全局安装,就可以在自己本机上执行 fbf start [dir]来生成一个fbf.html文件,这样便完成了一个简单的node工具了。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
Extjs 3.3切换tab隐藏相应工具栏出现空白解决
Apr 02 Javascript
Javascript WebSocket使用实例介绍(简明入门教程)
Apr 16 Javascript
jQuery中map()方法用法实例
Jan 06 Javascript
js关于命名空间的函数实例
Feb 05 Javascript
jQuery插件multiScroll实现全屏鼠标滚动切换页面特效
Apr 12 Javascript
javascript实现日期时间动态显示示例代码
Sep 08 Javascript
JS使用单链表统计英语单词出现次数
Jun 16 Javascript
jquery hover 不停闪动问题的解决方法(亦为stop()的使用)
Feb 10 Javascript
JavaScript中从setTimeout与setInterval到AJAX异步
Feb 13 Javascript
详解Vue.js Mixins 混入使用
Sep 15 Javascript
微信小程序MUI导航栏透明渐变功能示例(通过改变opacity实现)
Jan 24 Javascript
Layui实现数据表格默认全部显示(不要分页)
Oct 26 Javascript
Node.js中console.log()输出彩色字体的方法示例
Dec 01 #Javascript
Node.js控制台彩色输出的方法与原理实例详解
Dec 01 #Javascript
vue多个元素的样式选择器问题
Nov 29 #Javascript
解决Angularjs异步操作后台请求用$q.all排列先后顺序问题
Nov 29 #Javascript
vue 组件开发原理与实现方法详解
Nov 29 #Javascript
vue ajax 拦截原理与实现方法示例
Nov 29 #Javascript
VUE 实现动态给对象增加属性,并触发视图更新操作示例
Nov 29 #Javascript
You might like
php把数据表导出为Excel表的最简单、最快的方法(不用插件)
2014/05/10 PHP
PHP的几个常用加密函数
2016/02/03 PHP
php实现36进制与10进制转换功能示例
2017/01/10 PHP
PHP写API输出的时用echo的原因详解
2019/04/28 PHP
javascript中字符串拼接需注意的问题
2010/07/13 Javascript
两个数组去重的JS代码
2013/12/04 Javascript
JS高级调试技巧:捕获和分析 JavaScript Error详解
2014/03/16 Javascript
js漂浮广告实现代码
2015/08/15 Javascript
Bootstrap3学习笔记(二)之排版
2016/05/20 Javascript
如何利用@angular/cli V6.0直接开发PWA应用详解
2018/05/06 Javascript
深入理解JS的事件绑定、事件流模型
2018/05/13 Javascript
对vue 键盘回车事件的实例讲解
2018/08/25 Javascript
vue添加axios,并且指定baseurl的方法
2018/09/19 Javascript
npm qs模块使用详解
2020/02/07 Javascript
jquery更改元素属性attr()方法操作示例
2020/05/22 jQuery
vuex的数据渲染与修改浅析
2020/11/26 Vue.js
Python 'takes exactly 1 argument (2 given)' Python error
2016/12/13 Python
Python(Django)项目与Apache的管理交互的方法
2018/05/16 Python
Django之创建引擎索引报错及解决详解
2019/07/17 Python
Python使用PyQt5/PySide2编写一个极简的音乐播放器功能
2020/02/07 Python
Django多层嵌套ManyToMany字段ORM操作详解
2020/05/19 Python
提高python代码运行效率的一些建议
2020/09/29 Python
html5中localStorage本地存储的简单使用
2017/06/16 HTML / CSS
详解HTML5中的拖放事件(Drag 和 drop)
2016/11/14 HTML / CSS
Lookfantastic台湾:英国彩妆美发保养购物网
2018/03/26 全球购物
美国最大的电子宠物训练产品制造商:PetSafe
2018/10/12 全球购物
婴儿鞋,独特的婴儿服装和配件:Zutano
2018/11/03 全球购物
Right-on官方网站:日本知名的休闲服装品牌
2019/07/12 全球购物
捷克街头、运动和滑板一站式商店:BoardStar.cz
2019/10/06 全球购物
异常和异常类的概念
2014/09/12 面试题
RIP版本1跟版本2的区别
2013/12/30 面试题
计算机维护专业推荐信
2014/02/27 职场文书
中国梦演讲稿教师篇
2014/04/23 职场文书
学校四群教育实施方案
2014/06/12 职场文书
红十字会救护培训简讯
2015/07/20 职场文书
工程移交协议书
2016/03/24 职场文书