基于 Gulp 自动化构建工具仿 APP 界面云音乐播放器
话不多说,先瞄一眼效果图?!(当然只是针对移动端界面布局的,没考虑适配 pc 端)
外观上相差不太大吧(可能有些位置,图标什么的有点区别吧),当然啦,主要功能还是没全部实现,实现的部分功能如下:
- 播放器嘛,
播放/暂停
是基本,还有上 / 下一曲
,右下角点击歌单拉出歌单列表(点击歌单某一条来切歌当然也要有);- 数据也是本地的模拟数据, json 文件存储资源信息(通过
$.ajax()
获取), MP3文件 / 图片存放在根目录下的 source 文件夹下;- 图片旋转功能(
setInterval()
实现);- 随机播放按钮点击仅仅完成切换图标功能,实际切换歌曲播放模式没有去实现;
- 播放 状态进度条移动(
requestAnimationFrame()
实现),图片旋转;暂停 进度条停止,图片停止转动;- 歌曲播放结束,自动播放下一曲 / 暂停或播放状态切歌(进度条归零,图片角度归零);
进度条拖拽
实现拖到相应进度继续播放(暂停播放状态只是更改进度时间,不开始播放,需要手动触发播放)。
gulp 插件依赖问题
- 压缩图片插件
gulp-imagemin
出现问题(导致压缩后输出到 dist 目录无法完成) → 因而更改使用gulp-imagemin-coding-net-vendor
插件完成图片压缩( √ )。 gulp-live-server
插件未能正常使用,故换用gulp-connect
插件。- 较之
gulp 3.9.x
及之前版本,4.0.2 版本
在语法使用上有些区别(以下是用到的gulpfile.js
任务配置):- 每个 task 任务函数需要接收一个回调函数参数,并在任务函数结束之际调用回调函数(用以指明任务函数的结束时刻):
// 示例 gulp.task("fn_test", cb => { // other task code... cb(); // 任务函数结束的标志 });
gulp.task("default", [其他任务参数])
(4 版本之前的语法),更改为,第二个参数(任务数组)作为gulp.series()
的参数,并将此函数整体作为 default 任务的第二个参数传入:// 如此例 gulp.task("default", gulp.series(["fn_htmlClean", "fn_less", "fn_uglify"]));
- 在开启监听时,
gulp.watch()
函数的第二个参数与 default 任务一致,即变更为第二个参数是gulp.series([])
的形式。
- 每个 task 任务函数需要接收一个回调函数参数,并在任务函数结束之际调用回调函数(用以指明任务函数的结束时刻):
gulp-uglify
插件无法完成 js 代码压缩功能 (详细情况写在在本文下方压缩代码出现的问题
中)。
工具环境
开发依赖如下图所示 :
zepto
—— 轻量级移动端的 JS 库(与jQuery用法大致一致 —— 当然,我没有细致对比过,此项目中用到的 API 没发现什么区别);
此处是直接下载放到dist/js
文件夹目录下(已是压缩后的zepto.min.js
文件,若放到 src 工作目录下,经过下面 babel 及代码压缩处理后,运行项目时生成的zepto.min.js
文件会报错)。
压缩代码出现的问题
gulp-uglify
插件无法如预期一样正常压缩 js 代码
// 压缩 js 代码的任务
gulp.task("fn_uglify", cb => {
// folder 定义的变量,用于存储 src / dist 目录路径
let js = gulp.src(folder.src + "js/*.js")
.pipe(connect.reload()); // 监听重加载
// devMode 是记录( process.env.NODE_ENV === "development" )的Boolean值,用于决定是否压缩代码
// 即非开发环境下开启代码压缩 - 通过命令行 export NODE_ENV=development 设置为开发环境
if (!devMode) {
js.pipe(uglify());
}
js.pipe(gulp.dest(folder.dist + "js/"));
cb();
});
如上图所示的错误在使用 gulp-uglify
插件压缩 js 代码无法完成压缩,本人猜测可能是 ES6 新语法(如 class 类等)造成使用 gulp-uglify
插件压缩操作无法正常进行,进而导致后续输出无法完成的问题(因为一个单纯的 ES5 规范的 js 文件可以正常压缩输出)。
本人尝试使用 babel
转换 js 代码:
- 下载
@babel/core
、@babel/preset-env
和gulp-babel
,上面package.json
文件截图有记载依赖模块及版本。(可能需要 babel-cli ,我之前是全局安装了的,如果能正常使用,这句不用管)。 - gulpfile.js 文件配置
// ... 其他工具模块此处就省略了哈
const babel = require('gulp-babel');
// js → uglify
gulp.task("fn_uglify", cb => {
let js = gulp.src(folder.src + "js/*.js")
.pipe(connect.reload())
.pipe(babel({
presets: ['@babel/env']
}));
if (!devMode) {
js.pipe(uglify());
}
js.pipe(gulp.dest(folder.dist + "js/"));
cb();
});
- 创建
.babelrc
文件,并配置如下:
{
"presets": ["@babel/preset-env"] // 他人封装好的 babel 配置预设
}
配好上面三点之后,搓搓手敲敲 gulp
命令试试(鸡冻!!!)。运行结果如下:
哈哈,成了!!!这个问题纠结了好久啊…… (╥╯^╰╥)
部分代码
-
gulpfile.js
配置:const gulp = require('gulp'), htmlClean = require('gulp-htmlclean'), less = require('gulp-less'), cleanCss = require('gulp-clean-css'), postCss = require("gulp-postcss"), autoprofixer = require('autoprefixer'), imageMin = require("gulp-imagemin-coding-net-vendor"), uglify = require('gulp-uglify'), babel = require('gulp-babel'), connect = require('gulp-connect'); const folder = { src: "src/", dist: "dist/" }; const devMode = process.env.NODE_ENV === "development"; // html gulp.task('fn_htmlClean', cb => { let page = gulp.src(folder.src + "html/") .pipe(connect.reload()); if (!devMode) { page.pipe(htmlClean()); } page.pipe(gulp.dest(folder.dist + "html/")); cb(); }); // css gulp.task('fn_cleanCss', cb => { let css = gulp.src(folder.src + "css/") .pipe(connect.reload()) .pipe(less()) .pipe(postCss([autoprofixer()])); if (!devMode) { css.pipe(cleanCss()); } css.pipe(gulp.dest(folder.dist + "css/")); cb(); }); // image gulp.task('fn_imageMin', cb => { let image = gulp.src(folder.src + "images/"); if (!devMode) { image.pipe(imageMin()); } image.pipe(gulp.dest(folder.dist + "images/")); cb(); }); // js gulp.task("fn_uglify", cb => { let js = gulp.src(folder.src + "js/*.js") .pipe(connect.reload()) .pipe(babel({ presets: ['@babel/env'] })); if (!devMode) { js.pipe(uglify()); } js.pipe(gulp.dest(folder.dist + "js/")); cb(); }); // server gulp.task("fn_server", cb => { connect.server({ port: 9999, livereload: true }); cb(); }); gulp.task('watch', cb => { gulp.watch(folder.src + 'html/', gulp.series(["fn_htmlClean"])); gulp.watch(folder.src + 'css/', gulp.series(["fn_cleanCss"])); gulp.watch(folder.src + 'js/', gulp.series(["fn_uglify"])); cb(); }) gulp.task("default", gulp.series(['fn_htmlClean', 'fn_cleanCss', 'fn_imageMin', 'fn_uglify', 'watch', 'fn_server']));
-
其他相关模块均是立即执行函数,通过暴露到自定义的
window.player
对象上进行统一管理;进度条部分:(($, root) => { let duration, frameId, startTime = 0, lastPer = 0; function renderTotalTime(data) { duration = data.duration; const time = formatTime(duration); $('.progress .total-time').text(time); } function formatTime(time) { time = Math.round(time); let minutes = Math.floor(time / 60), seconds = time % 60; minutes = minutes > 10 ? minutes : ('0' + minutes); seconds = seconds > 10 ? seconds : ('0' + seconds); return (minutes + ':' + seconds); } function start(pTime) { // 决定是否从头开始 lastPer = pTime === undefined ? lastPer : pTime; cancelAnimationFrame(frameId); startTime = new Date().getTime(); function frame() { const curTime = new Date().getTime(); let per = lastPer + (curTime - startTime) / (duration * 1000); // 百分比 if (per <= 1) { upDate(per); } else { cancelAnimationFrame(frameId); } frameId = requestAnimationFrame(frame); } frame(); } // 更新进度条 function upDate(per) { // update time let time = formatTime(per * duration); $('.cur-titme').text(time); // update progress bar let persent = per * 100 + '%'; $('.real-pro').css('width', persent); } // 停止进度条更新 function stop() { let stopTime = new Date().getTime(); cancelAnimationFrame(frameId); // 依次加上前面的记录时间 lastPer += (stopTime - startTime) / (duration * 1000); } root.progress = { renderTotalTime, start, upDate, stop } })(window.Zepto, window.player || (window.player = {}));
-
ajax 请求的模拟
json
数据 (后面使用时, des 没去用来渲染,这可以删了):[{ "image": "/source/imgs/img_1.png", "audio": "/source/songs/song_1.mp3", "song": "世间美好与你环环相扣", "des": "世间美好与你环环相扣(薛之谦粉丝版)", "singer": "小力同学", "duration": 181, "isLike": true }, { "image": "/source/imgs/img_2.jpg", "audio": "/source/songs/song_2.mp3", "song": "与我无关", "des": "与我无关(女生完整版)", "singer": "Emily酱", "duration": 240, "isLike": false } // others ]
总结
这个小项目虽然只是简单地仿了云音乐 APP,最大的收获还是在踩 gulp-uglify
插件压缩 js 代码的小插曲上,来来回回整了好多次才正常配置好 babel,而且在解决压缩后,zepto 转成 ES5 语法时又报错(这个就没去找问题了,然后可以选择用 cdn 引入,此处是直接放到 dist/js 目录下的);也了解并使用了 gulp 自动化构建工具去 “流式” 地处理任务,完成项目的构建。
然后,源码附上 github 地址咯! cloudMusic