QQ音乐移动端项目涉及的技术栈
- rem的响应式单位;
- js中的正则;
- 数据获取;
- zepto和zepto中的订阅发布;
- html5+css3;
- less;
移动开发中的rem设置
- rem设置
- 优点:在不同的屏幕下,字体的大小以及宽高的设置不能使用定值px,需要在不同的屏幕下按比例缩放;所以需要设置rem;
- rem相对于浏览器中的html根标签的fontSize大小成倍数设置;与其他值无关;
- 实际开发
- 实际开发中屏幕大小为320px的设备,设计为2倍的屏幕设置,即按640px的设计,缩小后会很清晰;
- 需要通过JS代码,来设置在不同屏幕下的比例值缩放;
- 以下代码中:1)设计时屏幕的大小为640px;设置的html根标签fontSize大小为100px;2)设置后的结果是在设计的屏幕中1rem=100px;如果需要设置30px,则设置为0.3rem;
//获取可视区域的宽度 var $screenWidth=$(window).width(); //在实际开发中会规定好在制定屏幕下的字体大小;一般在320px的浏览器屏幕中,会放大一倍,即640px屏幕下制作,然后设置的字体大小为定值,通过比例,让其在不同的屏幕下,也按比例缩放; var kfWidth=640; var fSize=100; //jQuery中获取html根标签 var $html=$("html");//在原生JS中获取html标签:document.documentElement; var $htmlFont=$screenWidth/kfWidth*fSize; //jQuery中设置html的字体大小 $html.css("fontSize",$htmlFont);
- 在调试中将代码放在resize事件中;便于调试;
QQ音乐页面项目实战开发
移动项目开发前的准备
- 项目开发的前提条件:
- 都是以屏幕大小为640px的基准下制作的;html的字体大小设置为100px,所有的单位都用rem;其中1rem=100px;
- html结构
- meta视口创建:
;快捷键
meta:vp+tab
; - 引入less文件:包括index.less文件和less.js文件;放在head中;
- 引入JS文件:包括zepto.js文件和index.js文件;放在body里面的最后面;
- html代码:
QQ音乐实战 ... - meta视口创建:
- less设置
- 引入reset.less文件和public.less文件;
- html和body元素的宽高设置为100%;
- less代码:
@import "reset.less"; @import (reference)"public.less"; html,body{ width: 100%; height: 100%; }
- JS设置
- 目的:为了保证在所有屏幕下的rem换算的值能够相对于640px屏幕下值进行等比例缩小和放大;
- 以屏幕宽度为640px和html的fontSize值为100px为基础,进行换算,设置不同屏幕下的fontSize值;
- 注意:
- 屏幕尺寸大于640px后,让musicBox容器的宽度设置为640px,并设置居中;
- 屏幕尺寸大于640px后,设置为html的fontSize值为100px;
- 保证在屏幕尺寸大于640px后,能够正常显示;
- JS代码:
//设置在不同屏幕下的html的fontSize值; $(window).on("resize",resizeTo).trigger("resize"); function resizeTo() { var $screenWidth=$(window).width(); var $sjWidth=640; var $sjFont=100; var $htmlFont; //当屏幕宽度大于640px时,让$screenWidth值为640px,则计算出来的fontSize值一直为100px; //保证了屏幕中的rem值稳定,不会再随着屏幕改变而改变; if($screenWidth>$sjWidth){ $screenWidth=$sjWidth; $("html").css({ width:$sjWidth, margin: "0 auto" }); } $htmlFont=$screenWidth/$sjWidth*$sjFont; $("html").css("fontSize",$htmlFont); }
QQ音乐页面制作
1.1大背景的制作
- 思路:
- 在musicBox容器中创建
;添加定位,宽高均为100%;设置背景图为backgroundSize为cover;
- 给musicbg添加模糊度
-webkit-filter: blur(5px); filter: blur(5px);
- 创建遮罩层:添加定位,背景颜色用透明度设置,达到遮罩层的效果;
- 给musicbg和遮罩层设置层级为负数;保证后面的元素覆盖在上面;
- 在musicBox容器中创建
- 知识点:
- 元素脱离文档流后,即添加定位和浮动后,不会继承父级宽度,需要重新设置宽度;
- 模糊度代码的设置;
- 遮罩层使用背景色的透明度;
- 利用层级进行覆盖;
- 注意:
- 在chrome浏览器控制台上调试时,使用屏幕大小为320px的屏幕调试;屏幕中的尺寸为实际设置的尺寸值的一半;
- 代码:
- html代码:
- less代码:
.musicBox{ width: 100%; height: 100%; position: relative; .musicbg{ width: 100%; height: 100%; position: absolute; left: 0; top: 0; z-index: -2; background: url("../img/piao.jpg") no-repeat center; background-size: cover; -webkit-filter: blur(5px); filter: blur(5px); } .musicshadow{ width: 100%; height: 100%; position: absolute; left: 0; right: 0; z-index: -1; background-color: rgba(1,1,1,.3); } }
1.2头部区域的制作
- 重点:
- 右侧播放图标的设计
- 两个图标的容器添加定位;利用显示隐藏设置来实现切换;
- 在容器中添加背景图标;通过背景图的定位来控制位置;
- 背景图片的大小不能使用px,要使用rem值进行换算;换算后是相对于640px尺寸下的值,在调试中用320px尺寸的屏幕时,数值为一半;
- 右侧播放图标的设计
- 知识点:
- 在less文件中使用
.类名 (){}
创建函数,括号中可以传入实参;可以设置默认值;
.spriteFn(@x:0,@y:0){ background: url("../img/timg.jpg") no-repeat @x @y; background-size: 4rem; }
- 在less文件中使用
- 代码:
- html代码:
天空
朴信惠- less代码:
header{ padding: .3rem; color: @color-white; background-color: rgba(0,0,0,.3); .h-left{ float: left; >img{ float: left; width: 1.2rem; height: 1.2rem; margin-right: .4rem; } >p{ float: left; font-size: .35rem; >span{ line-height: .63rem; } } } .h-right{ float: right; width: .8rem; height: .8rem; border-radius: 50%; border: 1px solid @color-white; box-sizing: border-box; margin-top: .2rem; margin-right: .1rem; position: relative; .hr-play,.hr-pause{ position: absolute; width: .4rem; height: .4rem; left: 50%; top:50%; margin-top: -.2rem; margin-left: -.2rem; } .hr-play{ .spriteFn(@x:-.4rem,@y:-1.3rem); display: none; } .hr-pause{ .spriteFn(@x:-.4rem,@y:-.36rem); } } }
1.3歌词区域初制作
- html结构:
- 设置main容器,设置上下margin值;高度由JS计算设置;
- 在main容器中,创建lyc歌词容器,容器添加绝对定位,目的是为了调整top值,来达到歌词的更新;
- 设置lyc歌词容器中的p元素,通过假数据将样式设置好;
- main容器的高度为定值,而lyc歌词容器的高度是通过内容撑开的;给main设置overflow:hidden,这样lyc容器多出来的会被隐藏,通过定位不断的显示;
1.4footer区域的制作
- 思路:分为三个区域
- 收藏区域: 通过右浮动来做storage;
- 进度条区域:分为左右两个时间区域,和中间的进度条区域;
- 左右两个时间区域利用浮动制作,必须设置宽度才能浮动;
- 中间的进度条区域:
- 设置为行内块
inline-block
类型;利用容器的text-align: center
来居中显示; - 利用
vertical-align: middle/number
来设置垂直方向上的位置; - 在progress容器中添加一个div,相对于容器绝对定位,高度相同,宽度可以自由设置,背景色为绿色,二者叠加在一起;通过控制div的宽度,来呈现出进度条的进程;
- 设置为行内块
- 底部下载区域:通过伪类元素设置背景图,完成QQ图标的设置;
- footer区域设置定值高度,让底部下载区域距离底部有一段空隙;
1.5歌词区域的静态制作
- 1)计算设置不同屏幕下歌词区域的高度
- 在拉伸事件中设置JS代码;
- 获取屏幕的视口高度-header区域的高度-footer区域的高度-歌词区域设置的上下margin值;
- 注意:设置的margin值为rem值,需要乘上不同屏幕下的html的fontSize值;在jQuery中height()方法获取的高度不包含padding值,所以需要减去padding值;或使用innerHeight()或outerHeight();
- JS代码:
//1 计算不用屏幕下的main歌词区域的高度 //注意:在jQuery中$().height()拿到的高度不包含padding和边框,而zpeto中包含; //在jQuery中用$().outerHeight()可以拿到包含padding和边框的高度,但是zpeto中不支持此属性; var $screenH=$(window).height(); var $mainH=$screenH-$("header").height()-$("footer").height()-0.8*$htmlFont-0.6*$htmlFont;//jQuery引入后,会覆盖zpeto; $(".main").css("height",$mainH);
- 2)从后台获取歌词数据,转化为需要的格式;
- 通过
$.ajax()
来过去后台数据,此时获取到的时字符串;建立一个自执行函数的格式 - 将字符串分割为数组,forEach遍历数组,通过正则和replace方法配合使用,匹配出满足要求的内容,通过小分组,拿到分,秒,文字;并给每一项加一个不同的id值;
- 最终获取的数据为一个数组,数组中的每一项时包含分,秒,文字的对象;
- 通过订阅式发布fire函数来传出数据;
- JS代码:
//2 获取后台歌词数据,转换成需要的格式通过订阅式发布输出 var data=[], $id=0; //ajax获取数据,经过转化后,获取数组,然后通过jQuery中的订阅发布传入数据; var musicRender=(function () { return { init:function () { $.ajax({ url:"data/lyc.txt", dataType:"text", type: "get", success: function (result) { var ary=result.split("\\n");//\n需要转义符,将获取的字符串数据,分割为数组; var reg=/\[(\d{2})\:(\d{2})\.(?:\d{2})\](\D+)/g;//通过小分组拿到分,秒,文字; //forEach方法,遍历数组,第一项为数组元素内容,第二项为数组元素的索引值 ary.forEach(function (item,index) { //item为每一项的字符串,通过replace方法配合正则,进行逐一匹配操作; item.replace(reg,function () { data.push({ minute:arguments[1], seconds: arguments[2], lyc:arguments[3], id:$id }) }); $id++; }); callbacks.fire(data);//向订阅发布传入数据 } }) } } })(); musicRender.init();
- 通过
- 3)订阅式绑定函数,函数中获取数据,通过字符串拼接插入到DOM结构中;
- 通过订阅式绑定函数获取数据;获取的数据为原生数组,必须用$.each()方法来遍历数组;
- 通过字符串拼接插入DOM结构;
- 将data1数组中的分,秒,通过data自定义属性绑定到每个歌词的p元素上;
- JS代码:
//订阅式绑定方法;获取传入的数据,绑定在页面中; callbacks.add(function (data1) { //此时获得的数据为原生数组 var str=""; $.each(data1,function (index, item) { //字符串拼接,将数据放在自定义属性上; str+=`
${item.lyc}
` }); $lyc.html(str); });
1.6audio音频区域的制作
-
1)创建audio标签,放入到footer中;
- 代码:
- 注意:audio自带高度,所以给其添加定位,让其脱离文档流;不占据位置;
- 代码:
-
2)加载页面后音频播放
- 订阅绑定,绑定的函数依次执行;
- 函数中设置
oAudio.play()
,音频播放; - oAudio绑定canplay事件,当音频播放时,进入函数执行;
- 通过
oAudio.duration
获取音频的总时间长度,转化为分,秒的固定格式,插入到$duration中; - 创建定时器,每隔一秒执行f1()函数;
- f1()函数中设置:
- $current中的数组更新:
- 通过
oAudio.currentTime
获取音频的当前播放时间,转化为固定格式,插入到$current中;
- 通过
- 歌词区域的制作:
- 通过获取的当前音频时间值,进行筛选p元素身上的自定义属性;进而控制相对应的文字变色显示;
- 利用filter过滤器,通过属性判断,过滤选择;
- 将获取的p添加active类名;其他的兄弟元素删除此类名;
- 此时文字会跟随音乐的播放而对应变色显示;
- 设置lyc歌词容器的top值,来进行歌词的显示和隐藏,通过p的索引值,来控制上移的距离;
- 进度条的进度更新:
- 通过控制time-line的宽度值,来显示进度;通过设置百分数;
- 利用获取的当前播放时间与总时间的比值获得比例,然后设置宽度为此百分比;
- 比较当前播放时间和总时间:当二者相等时,证明歌曲播放完;
- 关闭定时器;
- 设置歌曲暂停,设置暂停按钮;
- $current中的数组更新:
- 秒转化为固定格式的分,秒的函数知识点:
- 分的获取用除号,然后向下取整;秒的获取用%号,取余;然后向上取整;这样会让歌词比音乐块一些;
- 秒里面由于是向上取整,会出现60;所以需要进行条件判断:当秒为60时,秒赋值为0;分自加1;
- 分和秒中的数字,会出现小于10的情况,所以需要进行重新判断赋值,通过字符串拼接,将1转化为"01";
- 返回一个固定格式;
- 函数代码:
//创建函数,用于转化时间格式 function timeFormat(time){ var min=Math.floor(time/60);//分钟用向下取整; var sec=Math.ceil(time%60);//秒用向上取整;这样会让歌词比音乐快一些; //判断当秒数为60时,变为0,分钟加等1; if(sec===60){ sec=0; min+=1; } min=min<10?"0"+min:""+min;//字符串拼接;得到的是字符串; sec=sec<10?"0"+sec:""+sec; return min+":"+sec; }
- JS代码:
//创建函数,用于转化时间格式 function timeFormat(time){ var min=Math.floor(time/60);//分钟用向下取整; var sec=Math.ceil(time%60);//秒用向上取整;这样会让歌词比音乐快一些; //判断当秒数为60时,变为0,分钟加等1; if(sec===60){ sec=0; min+=1; } min=min<10?"0"+min:""+min;//字符串拼接;得到的是字符串; sec=sec<10?"0"+sec:""+sec; return min+":"+sec; } //4 进入页面歌曲播放;获取音频的当前时间,控制歌词更新 callbacks.add(function () { oAudio.play();//加载页面后,歌词立刻播放; //拿到当前音频对象的总时间,为总秒数;转化格式后赋给$duratoin oAudio.addEventListener("canplay",function () { $duration.html(timeFormat(oAudio.duration)); timer=setInterval(fn1,1000);//开启定时器,不断获取新的currentTime }) }); function fn1() { //获取当前的音乐时间,赋值在$current中; var currentTime=timeFormat(oAudio.currentTime); $current.html(currentTime); //通过获取的当前时间值,进行筛选p元素身上的自定义属性;进而控制相对应的文字变色显示; var minute=currentTime.split(":")[0]; var seconds=currentTime.split(":")[1]; //利用filter过滤器,通过属性判断,过滤选择; var targetP=$lyc.children("p").filter(`[data-minute="${minute}"]`).filter(`[data-seconds="${seconds}"]`); //将获取的p添加active类名;其他的兄弟元素删除类名; targetP.addClass("active").siblings().removeClass("active"); //歌词的移动,通过给lyc添加定位,控制其top值的变化 var indexP=targetP.index();//通过索引值获取哪个p的时候,开始运动; if(indexP>=2){ $lyc.css("top",-(indexP-2)*0.84*$htmlFont); } //进度条的设置:设置time-line的宽度占progress的宽度百分比 var tlW=Number(oAudio.currentTime/oAudio.duration*100)+"%"; //$time_line.css("width",tlW); $time_line.animate({ width: tlW }); //当前时间等于总时间时,停止定时器,按钮变为pause; if(oAudio.currentTime===oAudio.duration){ clearInterval(timer); oAudio.pause(); $hr_pause.show(); $hr_play.hide(); } }
-
3)播放和暂停的制作
- 订阅绑定函数,依次执行;
- 在移动端点击事件,不设置click,设置tap;
- 在点击事件中,不管进行何种操作,都要先关闭定时器;
- 通过判断
oAudio.paused
的布尔值,来进行播放和暂停的操作; - 在进行播放操作中,最好先执行一下fn1()函数,因为定时器在1s后才会运行,存在异步;
- 设置不同的按钮显示;
- JS代码:
//5 播放和暂停的制作; callbacks.add(function () { $btn.on("click",function () {//在移动端不添加click时间,添加tap事件 clearInterval(timer); //在音频play播放时,oAudio.paused返回false值; if(oAudio.paused){ fn1(); timer=setInterval(fn1,1000); oAudio.play(); $hr_play.show(); $hr_pause.hide(); }else{ oAudio.pause(); $hr_pause.show(); $hr_play.hide(); } }) });
-
项目知识点:
- jQuery和zpeto二者区别:
- jQuery中
$().width()
获取的是不包含padding和边框的;而$().innerWidth()
包含padding;$().outerWidth()
包含padding和边框; - zpeto中只有
$().width()
和$().height()
,代表包含padding和边框的宽高;innerWidth()
和outerWidth()
不支持; - jQuery中的存在订阅式发布
$.Callbacks()
;而zpeto中不存在此方法;
- jQuery中
- jQuery中的订阅式发布
- 创建:
var callbacks=$.Callbacks();
- 订阅绑定:
callbacks.add(function(data1){})
;data1为接收fire传入的数据; - 发布执行:
callbacks.fire(data)
;其中data为传入匿名函数的数据 - 其中add可以绑定多个函数,当fire执行时,绑定的函数依次执行,并且每一个函数中都会接收fire传入的数据;
- 创建:
- jQuery中filter属性值筛选
- 代码:
var targetP=$lyc.children("p").filter(`[data-minute="${minute}"]`).filter(`[data-seconds="${seconds}"]`);
- 歌词和进度条的过渡效果:
- 在css样式中过渡元素的身上设置transition属性;
- 在JS代码中给过渡元素设置animate()方法;
- audio的知识
- audio的属性和方法,全都是原生JS获取的元素;
-
oAudio.play()
:音频播放; -
oAudio.pause()
:音频暂停; -
oAudio.paused
:获取暂停的状态,返回布尔值;当音频播放时,获取的值为false,当音频暂停时,获取的值为true; -
oAudio.addEventListener("canplay",function(){})
:指的是给oAudio绑定canplay事件,即当音频播放时,才会运行函数; -
oAudio.duration
:获取音频的总时间长度;单位为秒; -
oAudio.currentTime
:获取音频播放中的当前时间;单位为秒; -
oAudio.addEventListener("timeupdate",function(){})
:指的是不断的更新时间,当播放时,每隔0.3s触发一次事件;跟定时器一样;
- jQuery和zpeto二者区别: