0 写在前面
进入校历第9周以来,多门课程结束了,由此为自己带来一些轻松的同时,也面临着大量的大作业以及考试等等需要应对。
因此上一周没有练习太多的前端demo,现在将上一阶段的任务做一个了断之后,就又可以愉快的学习前端知识了!
今天学习了h5的一个新特性<audio>标签,主要用以加载音频文件。
为此,我练习了一个小项目,在运用一下<audio>的相关知识~
感兴趣的朋友可以点击屏幕右上角的小猫咪访问我的github,或点击这里下载源代码进行试用~
1 需求分析
先看效果
初始时,有一个太阳(左侧)和一个月亮(右侧),提示拖拽右侧的月亮来制造“日食”效果(eclipse)即可调节音频音量
当开始出现“日食”时,音频开始播放
随着日食的增大,音频的音量也随之增大,与此同时,为了营造“日食”氛围,需要对月亮和天空的颜色进行渐变
达到最大音量(100%)后,继续延同方向拖动月亮,即可减小音量,直到月亮离开太阳时,暂停音频的播放
2 设计分析
经过分析,程序编写过程可以用如下的流程图进行表示。
3 实现细节
3-1 初始化结构与样式
3-1-1 HTML5 <audio>标签
在HTML部分,这里我着重练习了<audio>标签的使用。
关于<audio>标签,应该了解以下内容:
- 浏览器支持
Internet Explorer 9+, Firefox, Opera, Chrome 以及 Safari 支持 <audio> 标签。
- <audio>属性
- <audio>对象方法
- <audio>对象属性
以上图片内容来源于W3School
在本次的练习中,我使用到的是src属性获取音频地址,preload="auto"忽略该属性。
HTML结构部分代码比较简单,如下所示
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <meta http-equiv="X-UA-Compatible" content="ie=edge"> 7 <title>MusicPlayer</title> 8 <link rel="stylesheet" href="demo.css"> 9 </head> 10 <body> 11 <div class="wrapper"> 12 <audio src="source/song.mp3" id="audio" preload="auto"></audio> 13 <div class="title">Justify volume by dragging the moon to eclipse the sun!</div> 14 <div class="per">Volume</div> 15 <div class="circle sun"></div> 16 <div class="circle moon"></div> 17 <div class="author">Chris.Chen Copyright 2019 All rights reserved.</div> 18 </div> 19 <script src="demo.js"></script> 20 </body> 21 </html>
3-1-2 初始样式设计
在初始样式设计中练习了3个知识点:
-
hsl函数设置颜色:hsl的三个参数分别代表色相(即颜色)、饱和度和亮度。这里我用到的是 background: hsl(194,66%,49%);来设置天空的背景颜色。
-
box-shadow:这是CSS3的一个新特性
他的用法是这样的 box-shadow: h-shadow v-shadow [blur] [spread] [color] [inset]
其中,h-shadow v-shadow两个参数表示水平和垂直方向上阴影的偏移值,为必填参数
而, [blur]模糊距离 [spread]阴影尺寸 [color]阴影颜色 和[inset]设置为内阴影,为可选参数
在本练习中,我采用的样式是
box-shadow: 0 0 50px #ff7; 作为太阳的阴影,水平垂直不偏移(即均匀分布的阴影),模糊距离50px,颜色为#ff7
box-shadow: inset 0px 0px 50px rgba(255,255,119,0.3); 作为月亮的阴影,设置了inset即向内的阴影。
- 重点:自适应的圆形
宽度相对于父级的20%,但高度若设成相对于父级的20%可能会出问题,因为父级未必是正方形
解决方案是将高度设为0,并设置padding-top。
初始化样式部分的代码如下:
1 *{ 2 margin: 0; 3 padding: 0; 4 } 5 html,body{ 6 height: 100%; 7 width: 100%; 8 /* 色相 饱和度 亮度 */ 9 background: hsl(194,66%,49%); 10 } 11 .wrapper{ 12 width: 70%; 13 height: 100%; 14 margin: 0 auto; 15 /* border: 1px solid #000; */ 16 position: relative; 17 } 18 .wrapper .title{ 19 position: absolute; 20 top: 100px; 21 width: 100%; 22 /* text-align: center; */ 23 color: #fff; 24 font-size: 24px; 25 font-weight: bold; 26 } 27 .wrapper .per{ 28 position: absolute; 29 top: 20%; 30 width: 100%; 31 height: 40px; 32 text-align: center; 33 color: #fff; 34 font-size: 20px; 35 font-weight: bold; 36 } 37 .wrapper .circle{ 38 /* 重点:实现自适应的圆形 */ 39 /* 宽度相对于父级的20%,但高度若设成相对于父级的20%可能会出问题,因为父级未必是正方形 */ 40 /* 解决方案是将高度设为0,并设置padding-top */ 41 width: 20%; 42 padding-top: 20%; 43 position: absolute; 44 /* border: 1px solid #000; */ 45 border-radius: 50%; 46 top: 30%; 47 left: 30%; 48 } 49 .wrapper .circle.sun{ 50 background: #ffff77; 51 box-shadow: 0 0 50px #ff7; 52 } 53 .wrapper .circle.moon{ 54 /* 留出两个圆形之间的空隙 */ 55 left: 52%; 56 /* inset向里阴影,阴影左右偏移量,阴影上下偏移量,阴影宽度,阴影颜色 */ 57 box-shadow: inset 0px 0px 50px rgba(255,255,119,0.3); 58 cursor: pointer; 59 } 60 .wrapper .author{ 61 width: 100%; 62 text-align: center; 63 color: #fff; 64 font-size: 16px; 65 position: absolute; 66 bottom: 15%; 67 }
3-2 鼠标事件绑定
到了JS部分,今天在练习中,还着重练习的一点是面向对象的思想,因此将今天实现的功能全部挂载到了一个obj对象上,在对象内部试图通过this来进行访问。
但是有一个问题就是,在为dom元素绑定鼠标事件的时候会改变this指向,导致this指向了当前dom元素,从而无法取得obj的属性或方法。
在这里用到的一个技巧,就是在obj内部添加一个变量var self = this保存一下指向当前obj的this。
鼠标共涉及3种事件:鼠标落下、移动和抬起,落下绑定在moon对象上,而移动和抬起则绑定在body对象上,这是为了放置鼠标移动过快导致鼠标脱离了moon的区域。
原对象e可以通过e.clientX,e.clientY获取鼠标点击的横纵坐标,通过计算前后坐标之间的差值,并更新dom对象的style,即可实现拖拽效果。
在抬起取消移动的设计上,可以用body.onmousemove = null 也可以用在本次练习中的写法那样,加一个flag锁控制,若抬起鼠标则释放锁,在onmousemove的函数中直接返回。
在移动中,随时调用比例计算函数。
3-3 覆盖比例计算
覆盖比例需要分情况讨论。
首先通过moon.clientWidth获取这个dom对象的宽(即月亮的直径),这个值用于后面的比例计算。
需要分月亮在太阳的左或是右,两种情况进行讨论和计算。
比例的计算方法为:重叠长度 / 直径
注意边界:当太阳雨月亮相离时,直接设置比例为0。
将计算得的比例作为参数传入并调用调整方法。
3-4 音量与背景调节
调节部分比较简单,获取到了比例计算的结果之后,即可根据这一比例进行设置dom元素的属性改变情况了。
audio.play()方法可以播放指定audio对象的音频
audio.pause()方法可以暂停指定audio对象的音频
audio.volume属性可以设置指定audio对象音频的音量,取值为[0,1]
其他调整还包括对文字提示innerHTML的调整和对style.background颜色的调整
3-5 行为控制JS代码
1 var obj = { 2 init:function(){ 3 // 这里与jquery不同,字符串直接写类名即可,无需在前面加. 4 this.moon = document.getElementsByClassName('moon')[0]; 5 this.sun = document.getElementsByClassName('sun')[0]; 6 this.bindEvent(); 7 }, 8 bindEvent:function(){ 9 var moon = this.moon; 10 var body = document.getElementsByTagName('body')[0]; 11 var dis; 12 var self = this; 13 var flag = false; 14 moon.onmousedown = function(e){ 15 dis = e.clientX - moon.offsetLeft; 16 flag = true; 17 }; 18 body.onmousemove = function(e){ 19 if(!flag) return; 20 moon.style.left = (e.clientX - dis) + 'px'; 21 self.getPer(); 22 }; 23 body.onmouseup = function(e){ 24 flag = false; 25 }; 26 }, 27 getPer:function(){ 28 var self = this; 29 var sun = self.sun; 30 var moon = self.moon; 31 var per; 32 var d = moon.clientWidth, 33 mL = moon.offsetLeft, 34 mR = moon.offsetLeft + d, 35 sL = sun.offsetLeft, 36 sR = sun.offsetLeft + d; 37 if(mL > sR || mR < sL){ 38 per = 0; 39 } 40 else{ 41 if(sL < mL){ 42 per = (sR - mL) / d; 43 } 44 else if(mR > sL){ 45 per = (mR - sL) / d; 46 } 47 } 48 self.change(per); 49 }, 50 change:function(vol){ 51 var audio = document.getElementsByTagName('audio')[0]; 52 var body = document.getElementsByTagName('body')[0]; 53 var per = document.getElementsByClassName('per')[0]; 54 var moon = this.moon; 55 vol > 0 ? audio.play():audio.pause(); 56 audio.volume = vol; 57 var str = "Volume:" + (vol*100).toPrecision(4) + "%"; 58 per.innerHTML = str; 59 moon.style.background = "hsl(194,66%," + (1 - vol) * 60 + "%)"; 60 body.style.background = "hsl(" + (194 + Math.floor(166*vol)) + ", 66%," + (1 - vol) * 50 + "%)"; 61 } 62 } 63 64 obj.init();