当前位置:   article > 正文

H5: 使用Web Audio API播放音乐

web audio api

简介

记录关于自己使用 Web Audio APIAudioContext 播放音乐的知识点。

需求分析

在这里插入图片描述

1.列表展示音乐;
2.上/下一首、播放/暂停/续播;
3.播放模式切换:循环播放、单曲循环、随机播放;
4.播放状态显示:当前播放的音乐名、播放时间、总时间、进度条效果;
5.播放控制器显示在底部区域;
6.支持音量调节;
7.浏览器隐藏、显示的交互后,也能正常有效播放(播放、声音)。

注意

安卓IOS上有不同的兼容性,所以采用了 Web Audio APIAudioContext ,兼容性强大(但是截止写文章前,IOS17+版本不支持,没有声音)。

稍微复杂点点的逻辑就是AudioContext与手机系统的关联,可以看看 AudioContext: createMediaElementSource

在这里插入图片描述

具体实现

test/music/musicPlayer/musics.ts
test/music/musicPlayer/useMusicPlayer.ts
test/music/index.vue

1.test/music/musicPlayer/musics.ts

interface musicItem {
  title: string
  src: string
  time: string
  mp3Name: string
}
const musicList: musicItem[] = [
  {
    title: 'How to Love',
    src: '',
    time: '03:39',
    mp3Name: 'sx_music_HowtoLove_CashCash'
  },
  {
    title: '空空如也',
    src: '',
    time: '03:34',
    mp3Name: 'sx_music_kongkongruye'
  },
  {
    title: '2 Soon',
    src: '',
    time: '03:19',
    mp3Name: 'sx_music_Soon_JonYoung'
  },
  {
    title: '孤勇者',
    src: '',
    time: '04:16',
    mp3Name: 'sx_music_guyongzhe'
  },
  { title: '秒针', src: '', time: '02:58', mp3Name: 'sx_music_miaozhen' },
  {
    title: '热爱105˚的你',
    src: '',
    time: '03:15',
    mp3Name: 'sx_music_reai105dudeni'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  }
] // 音乐列表信息

export { type musicItem, musicList }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107

2.test/music/musicPlayer/useMusicPlayer.ts

import { ref, nextTick } from 'vue'
import { musicList } from './musics'

enum PlayMode {
  REPEAT, // 循环播放
  SINGLE_CYCLE, // 单曲循环
  RANDOM // 随机播放
}

const musicPlayer = ref<HTMLAudioElement | null>()
const musicPlayingIndex = ref(-1) // 播放的音乐的下标
const musicIsPlaying = ref(false) // 是否播放中
const currentTime = ref(0) // 正在播放的音乐时间点
const musicPlayMode = ref(PlayMode.REPEAT) // 播放模式
const progressInterval = 500 // 计时器触发的频率
let defaultVolume = 1 // 音量 0-1
let timer: NodeJS.Timer | null = null // 计时器  ---此处需要在 .eslintrc.js/.cjs 文件中配置 globals: { NodeJS: true }
let source: MediaElementAudioSourceNode | null = null
let audioCtx: AudioContext | null = null
let gainNode: GainNode | null = null
let audioContextAttr: string | null = null
if ('AudioContext' in window) {
  audioContextAttr = 'AudioContext'
} else if ('webkitAudioContext' in window) {
  audioContextAttr = 'webkitAudioContext'
}

const useMusicPlayer = () => {
  const _getMusicFile = (mp3Name: string) => {
    // 此处需要相对路径
    // vite项目
    // return new URL(`../../../assets/music/${mp3Name}.mp3`, import.meta.url).href
    // webpack项目
    return require(`../../../assets/music/${mp3Name}.mp3`)
  }

  /** 设置:音量百分比 0-100 变为 0-1
   * @param v number 0-100
   */
  const _saveDefaultVolume = (v: number) => {
    let num = v
    if (v < 0) {
      num = 0
    } else if (v > 100) {
      num = 100
    }
    defaultVolume = num / 100
    return defaultVolume
  }

  /**
   * 计时器:回调-更新显示-MP3的播放时间
   */
  const _intervalUpdatePlayTime = () => {
    const player = musicPlayer.value
    if (!player) return
    currentTime.value = player.currentTime
  }

  /**
   * 计时器:清除
   */
  const _clearTimer = () => {
    if (!timer) return
    clearInterval(timer)
    timer = null
  }

  /**
   * 计时器:绑定&开始
   */
  const _startTimer = () => {
    _clearTimer()
    timer = setInterval(_intervalUpdatePlayTime, progressInterval)
  }

  /**
   * 方法:取两个值之间的随机数
   */
  const _random = (min = 0, max = 100) => Math.floor(Math.random() * (max - min + 1)) + min

  /**
   * 销毁:断开audio与AudioContext之间的链接
   */
  const _destroyConnect = () => {
    if (source) {
      source.disconnect()
    }
    if (gainNode) {
      gainNode.disconnect()
    }
    if (audioCtx) {
      audioCtx.close()
    }
    source = null
    gainNode = null
    audioCtx = null
    musicPlayer.value = null
  }

  /**
   * 音乐:初始化audio与AudioContext的绑定
   * 目的是为了 IOS 上能调整音量
   */
  const _init = () => {
    if (!audioContextAttr) return
    // 先暂停已有的播放
    pause()
    // 对已创建的绑定关系进行解绑
    _destroyConnect()
    // 若在body中找得到对应的dom,则进行移除
    const findDom = document.getElementById('musicPlayerAudio') as HTMLAudioElement
    if (findDom) {
      findDom.remove()
    }
    // 创建audio,加入body中
    const dom = document.createElement('audio')
    dom.id = 'musicPlayerAudio'
    document.body.appendChild(dom)
    // 给audio绑定播放结束的回调函数
    dom.onended = onAudioEnded
    // 创建AudioContext、source、gainNode,进行关联(便于IOS控制音量)
    const UseAudioContext = (window as any)[audioContextAttr]
    audioCtx = new UseAudioContext()
    if (!audioCtx) return
    source = audioCtx.createMediaElementSource(dom)
    gainNode = audioCtx.createGain()
    source.connect(gainNode)
    gainNode.connect(audioCtx.destination)
    // 设置音量
    if (defaultVolume === 0) {
      dom.muted = true
    } else {
      dom.muted = false
    }
    gainNode.gain.value = defaultVolume
    // 存储dom,便于后续访问audio对应的属性
    musicPlayer.value = dom
    // 若播放控制器的状态未启动,则启动
    if (audioCtx && audioCtx.state === 'suspended') {
      audioCtx.resume()
    }
  }

  /**
   * 音乐:播放器-音量调整
   */
  const setVolume = (volume: number) => {
    const v = _saveDefaultVolume(volume)
    const player = musicPlayer.value
    if (!player) return
    if (v === 0) {
      player.muted = true
    } else {
      player.muted = false
    }
    if (!gainNode || !gainNode.gain) return
    gainNode.gain.value = v
  }

  /**
   * 音乐:播放器-暂停
   */
  const pause = () => {
    const player = musicPlayer.value
    if (!musicIsPlaying.value || !player) {
      return
    }
    musicIsPlaying.value = false
    player.pause()
    _clearTimer()
  }

  /**
   * 音乐:播放器-播放
   */
  const playByLast = () => {
    const player = musicPlayer.value
    if (!player || !player.src) return
    if (audioCtx && audioCtx.state === 'suspended') {
      audioCtx.resume()
    }
    nextTick(() => {
      // play触发时,会先自动加载资源
      player.play().then(() => {
        musicIsPlaying.value = true
        _startTimer()
      })
    })
  }

  /**
   * 音乐:播放器-播放-通过下标
   */
  const playByIndex = (index: number) => {
    if (index < 0 || index + 1 > musicList.length) {
      return
    }
    musicIsPlaying.value = false
    // 重新初始化,便于释放上一个播放器所占用的内存
    _init()
    const player = musicPlayer.value
    if (!player) {
      return
    }
    // 重置当前播放了的时长
    currentTime.value = 0
    // 更新要播放的下标
    musicPlayingIndex.value = index
    if (!musicList[index].src) {
      // 若资源路径不存在,则进行对应的路径引入
      musicList[index].src = _getMusicFile(musicList[index].mp3Name)
    }
    if (!musicList[index].src) {
      console.error('find music file failed')
      return
    }
    player.src = musicList[index].src
    playByLast()
  }

  /**
   * 音乐:随机播放
   */
  const randomPlay = () => {
    const index = _random(0, musicList.length - 1)
    playByIndex(index)
  }

  /**
   * 音乐:播放器-下一首
   */
  const playNext = () => {
    if (musicPlayMode.value === PlayMode.RANDOM) {
      randomPlay()
    } else {
      const index: number =
        musicPlayingIndex.value + 1 === musicList.length ? 0 : musicPlayingIndex.value + 1
      playByIndex(index)
    }
  }

  /**
   * 音乐:播放器-上一首
   */
  const playPrev = () => {
    if (musicPlayMode.value === PlayMode.RANDOM) {
      randomPlay()
    } else {
      const index: number =
        musicPlayingIndex.value < 1 ? musicList.length - 1 : musicPlayingIndex.value - 1
      playByIndex(index)
    }
  }

  /**
   * 回调:播放结束后,下一首播放什么
   */
  const onAudioEnded = () => {
    switch (musicPlayMode.value) {
      case PlayMode.REPEAT:
        playNext()
        break
      case PlayMode.SINGLE_CYCLE:
        playByIndex(musicPlayingIndex.value)
        break
      case PlayMode.RANDOM:
        randomPlay()
        break
      default:
        break
    }
    return true
  }

  /** 自动播放音乐 */
  const startPlayInRoom = () => {
    // 用户第一次点击时,自动播放音乐
    const initMusicAutoPlayOnReload = () => {
      document.removeEventListener('click', initMusicAutoPlayOnReload, true)
      playByIndex(0)
    }
    document.addEventListener('click', initMusicAutoPlayOnReload, true)
  }

  return {
    musicList,
    musicPlayer,
    musicPlayingIndex,
    musicIsPlaying,
    currentTime,
    musicPlayMode,
    setVolume,
    pause,
    playByIndex,
    playByLast,
    playPrev,
    playNext,
    _clearTimer,
    startPlayInRoom
  }
}

export { PlayMode, useMusicPlayer }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304

3.test/music/index.vue

<template>
  <div class="music-box">
    <!-- 音乐列表 -->
    <div class="music-list">
      <div
        v-for="(music, index) in musicList"
        :key="index"
        class="music-item"
        :class="{ 'music-item-active': musicPlayer.musicPlayingIndex.value === index }"
        @click.stop="switchAudio(index)"
      >
        <div class="item-left">
          <div class="item-left-title">
            {{ music.title }}
          </div>
          <svg
            v-if="musicPlayer.musicPlayingIndex.value === index && musicPlayer.musicIsPlaying.value"
            id="equalizer"
            width="13px"
            height="11px"
            viewBox="0 0 10 7"
            version="1.1"
            xmlns="http://www.w3.org/2000/svg"
            xmlns:xlink="http://www.w3.org/1999/xlink"
          >
            <g fill="#3994f9">
              <rect
                id="bar1"
                transform="translate(0.500000, 6.000000) rotate(180.000000) translate(-0.500000, -6.000000) "
                x="0"
                y="5"
                width="1"
                height="2px"
              ></rect>
              <rect
                id="bar2"
                transform="translate(3.500000, 4.500000) rotate(180.000000) translate(-3.500000, -4.500000) "
                x="3"
                y="2"
                width="1"
                height="5"
              ></rect>
              <rect
                id="bar3"
                transform="translate(6.500000, 3.500000) rotate(180.000000) translate(-6.500000, -3.500000) "
                x="6"
                y="0"
                width="1"
                height="7"
              ></rect>
            </g>
          </svg>
          <svg
            v-else-if="musicPlayer.musicPlayingIndex.value === index"
            id="equalizer"
            width="13px"
            height="11px"
            viewBox="0 0 10 7"
            version="1.1"
            xmlns="http://www.w3.org/2000/svg"
            xmlns:xlink="http://www.w3.org/1999/xlink"
          >
            <g fill="#3994f9">
              <rect x="0" y="5" width="1" height="2px"></rect>
              <rect x="3" y="2" width="1" height="5"></rect>
              <rect x="6" y="0" width="1" height="7"></rect>
            </g>
          </svg>
        </div>
        <div class="item-right">
          {{ music.time }}
        </div>
      </div>
    </div>
    <!-- 播放控制 -->
    <div class="music-control">
      <div class="control-content">
        <div class="control-content-left">
          <div
            class="music-btn prev"
            @touchstart.passive="onTouchEvent"
            @touchend.passive="onTouchEvent"
            @click="prev"
          />
          <div
            :class="['music-btn', musicPlayer.musicIsPlaying.value ? 'pause' : 'play']"
            @touchstart.passive="onTouchEvent"
            @touchend.passive="onTouchEvent"
            @click="togglePlayer"
          />
          <div
            class="music-btn next"
            @touchstart.passive="onTouchEvent"
            @touchend.passive="onTouchEvent"
            @click="next"
          />
        </div>
        <div class="control-content-center">
          <div class="center-title">
            {{ currentMusicTitle || '-' }}
          </div>
          <div ref="audioProgressWrap" class="center-progress-wrap">
            <div ref="audioProgress" class="center-progress-wrap-active" />
          </div>
          <div class="center-time">
            <div class="center-time-now">
              {{ formatSecond(musicPlayer.currentTime.value) }}
            </div>
            <div class="center-time-total">
              {{ currentMusicTotalTimeStr }}
            </div>
          </div>
        </div>
        <div class="control-content-right">
          <div
            v-if="musicPlayer.musicPlayMode.value === PlayMode.REPEAT"
            class="music-btn playRepeat"
            @touchstart.passive="onTouchEvent"
            @touchend.passive="onTouchEvent"
            @click="nextPlayMode"
          />
          <div
            v-if="musicPlayer.musicPlayMode.value === PlayMode.SINGLE_CYCLE"
            class="music-btn singleCycle"
            @touchstart.passive="onTouchEvent"
            @touchend.passive="onTouchEvent"
            @click="nextPlayMode"
          />
          <div
            v-if="musicPlayer.musicPlayMode.value === PlayMode.RANDOM"
            class="music-btn playRandom"
            @touchstart.passive="onTouchEvent"
            @touchend.passive="onTouchEvent"
            @click="nextPlayMode"
          />
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
  import { ref, computed, watch } from 'vue'
  import { PlayMode, useMusicPlayer } from './musicPlayer/useMusicPlayer'
  import { musicList } from './musicPlayer/musics'

  const systemSoundMode = ref(true) // 该变量应该在store中,便于设置页面控制全局声音的开启与否
  const musicPlayer = useMusicPlayer()

  const audioProgressWrap = ref()
  const audioProgress = ref()

  /** 当前播放的音乐名 */
  const currentMusicTitle = computed(() =>
    musicPlayer.musicPlayingIndex.value + 1 > 0
      ? musicList[musicPlayer.musicPlayingIndex.value].title
      : ''
  )

  /** 当前播放的音乐总时间 */
  const currentMusicTotalTimeStr = computed(() =>
    musicPlayer.musicPlayingIndex.value + 1 > 0
      ? musicList[musicPlayer.musicPlayingIndex.value].time
      : '00:00'
  )

  /** 操作:切换播放模式 */
  const nextPlayMode = () => {
    musicPlayer.musicPlayMode.value = (musicPlayer.musicPlayMode.value + 1) % 3
    switch (musicPlayer.musicPlayMode.value) {
      case PlayMode.REPEAT:
        console.log('循环播放')
        break
      case PlayMode.RANDOM:
        console.log('随机播放')
        break
      case PlayMode.SINGLE_CYCLE:
        console.log('单曲循环')
        break
      default:
        break
    }
  }

  /** 事件:当点击按钮时的过渡效果 */
  const onTouchEvent = (event: Event) => {
    const tg = event.currentTarget as HTMLElement
    if (!tg) return
    if (event.type === 'touchstart') {
      tg.classList.add('touch')
    }
    if (event.type === 'touchend') {
      tg.classList.remove('touch')
    }
  }

  /** 格式化:秒数=>ss:mm */
  const formatSecond = (second: number) => {
    let hourStr = `${Math.floor(second / 60)}`
    let secondStr = `${Math.ceil(second % 60)}`
    if (hourStr.length === 1) {
      hourStr = `0${hourStr}`
    }
    if (secondStr.length === 1) {
      secondStr = `0${secondStr}`
    }
    return `${hourStr}:${secondStr}`
  }

  /** 操作:播放所选音乐 */
  const switchAudio = (index: number) => {
    const player = musicPlayer.musicPlayer.value
    if (!systemSoundMode.value) {
      window.alert('所有声音已关闭')
    }
    if (player?.src && player?.src.includes(musicList[index].mp3Name)) {
      if (musicPlayer.musicIsPlaying.value) {
        return
      }
      musicPlayer.playByLast()
    } else {
      musicPlayer.playByIndex(index)
    }
  }

  /** 操作:上一首 */
  const prev = () => {
    if (!systemSoundMode.value) {
      window.alert('所有声音已关闭')
    }
    musicPlayer.playPrev()
  }

  /** 操作:下一首 */
  const next = () => {
    if (!systemSoundMode.value) {
      window.alert('所有声音已关闭')
    }
    musicPlayer.playNext()
  }

  /** 操作:播放/暂停 */
  const togglePlayer = () => {
    const player = musicPlayer.musicPlayer.value
    if (!systemSoundMode.value) {
      window.alert('所有声音已关闭')
    }
    if (musicPlayer.musicIsPlaying.value && player?.src) {
      // 正在播放,则暂停
      musicPlayer.pause()
    } else if (!player?.src) {
      // 未开始播放,则播放第一首
      musicPlayer.playByIndex(0)
    } else {
      // 暂停了,则继续播放刚才的
      musicPlayer.playByLast()
    }
  }

  /** 监听:当前播放中的音乐的进度时间=>进度条变化 */
  watch(
    () => musicPlayer.currentTime.value,
    () => {
      const player = musicPlayer.musicPlayer.value
      if (!audioProgressWrap.value || !audioProgress.value || !player) {
        return
      }
      const offsetLeft =
        (player.currentTime / player.duration) * audioProgressWrap.value.offsetWidth
      audioProgress.value.style.width = `${offsetLeft}px`
    }
  )
</script>

<style lang="less" scoped>
  @bottomHeight: 97px;
  @controlHeight: 63px;
  @controlBottom: 34px;
  .music-box {
    width: 100%;
    height: 100%;
    background-color: #141624;
    position: relative;
    display: flex;
    flex-direction: column;
  }
  .music-list {
    flex: 1;
    overflow-y: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
    &::-webkit-scrollbar {
      display: none;
    }
    .music-item:nth-of-type(1) {
      margin-top: 7px;
    }
    .music-item {
      padding: 12px 20px 19px;
      display: flex;
      align-items: center;
      justify-content: space-between;
      font-size: 14px;
      font-weight: 500;
      line-height: 120%;
      color: #8f9095;
      .item-left {
        display: flex;
        align-items: center;
        .item-left-title {
          height: 17px;
          margin-right: 10px;
        }
        #equalizer {
          position: relative;
        }
        #bar1 {
          animation: bar1 1.2s infinite linear;
        }
        #bar2 {
          animation: bar2 0.8s infinite linear;
        }
        #bar3 {
          animation: bar3 1s infinite linear;
        }
        #bar4 {
          animation: bar4 0.7s infinite linear;
        }
        @keyframes bar1 {
          0% {
            height: 2px;
          }
          50% {
            height: 7px;
          }
          100% {
            height: 2px;
          }
        }
        @keyframes bar2 {
          0% {
            height: 5px;
          }
          40% {
            height: 1px;
          }
          80% {
            height: 7px;
          }
          100% {
            height: 5px;
          }
        }
        @keyframes bar3 {
          0% {
            height: 7px;
          }
          50% {
            height: 0;
          }
          100% {
            height: 7px;
          }
        }
        @keyframes bar4 {
          0% {
            height: 2px;
          }
          50% {
            height: 7px;
          }
          100% {
            height: 2px;
          }
        }
      }
    }
    .music-item-active {
      .item-left {
        .item-left-title {
          color: #3994f9;
        }
      }
      .item-right {
        color: #3994f9;
      }
    }
  }
  .music-control {
    height: @bottomHeight;
    padding: 0 10px;
    background-color: #141624;
    .control-content {
      height: @controlHeight;
      border-radius: 7px;
      background-color: #1b1d2a;
      display: flex;
      align-items: center;
      justify-content: space-between;
      .control-content-left {
        display: flex;
        align-items: center;
        .prev,
        .pause,
        .play {
          margin-right: 10px;
        }
        .next {
          margin-right: 17px;
        }
      }
      .control-content-center {
        margin-top: 1px;
        flex: 1;
        .center-title {
          margin-bottom: 5px;
          line-height: 120%;
          font-size: 13px;
          font-weight: 500;
          color: #fff;
        }
        .center-progress-wrap {
          width: 100%;
          height: 2px;
          background-color: #3e404e;
          .center-progress-wrap-active {
            width: 0;
            height: 100%;
            background-color: #3994f9;
          }
        }
        .center-time {
          height: 50%;
          margin-top: 10px;
          display: flex;
          justify-content: space-between;
          align-items: center;
          .center-time-now,
          .center-time-total {
            font-size: 10px;
            font-weight: 400;
            line-height: 12px;
            color: #3994f9;
          }
          .center-time-total {
            color: #8f9095;
          }
        }
      }
      .control-content-right {
        padding-left: 10px;
      }
      .music-btn {
        width: 33px;
        height: 33px;
        &.prev {
          background: url('../../assets/images/music/music-prev.png');
          background-size: 100%;
          background-repeat: no-repeat;
          &.touch {
            background: url('../../assets/images/music/music-prev-touch.png');
          }
        }
        &.play {
          background: url('../../assets/images/music/music-play.png');
          background-size: 100%;
          background-repeat: no-repeat;
          &.touch {
            background: url('../../assets/images/music/music-play-touch.png');
          }
        }
        &.pause {
          background: url('../../assets/images/music/music-pause.png');
          background-size: 100%;
          background-repeat: no-repeat;
          &.touch {
            background: url('../../assets/images/music/music-pause-touch.png');
          }
        }
        &.next {
          background: url('../../assets/images/music/music-next.png');
          background-size: 100%;
          background-repeat: no-repeat;
          &.touch {
            background: url('../../assets/images/music/music-next-touch.png');
          }
        }
        &.playRepeat {
          background: url('../../assets/images/music/music-repeat.png');
          background-size: 100%;
          background-repeat: no-repeat;
          &.touch {
            background: url('../../assets/images/music/music-repeat-touch.png');
          }
        }
        &.singleCycle {
          background: url('../../assets/images/music/music-single-cycle.png');
          background-size: 100%;
          background-repeat: no-repeat;
          &.touch {
            background: url('../../assets/images/music/music-single-cycle-touch.png');
          }
        }
        &.playRandom {
          background: url('../../assets/images/music/music-random.png');
          background-size: 100%;
          background-repeat: no-repeat;
          &.touch {
            background: url('../../assets/images/music/music-random-touch.png');
          }
        }
      }
    }
  }
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495
  • 496
  • 497
  • 498
  • 499
  • 500
  • 501
  • 502
  • 503
  • 504
  • 505
  • 506
  • 507
  • 508
  • 509
  • 510
  • 511
  • 512
  • 513
  • 514
  • 515

最后

觉得有用的朋友请用你的金手指点一下赞,或者评论留言一起探讨技术!

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号