网页音乐播放器(html+css+原生js)

想自己试做一个小网站,于是边学边做,也有了一个可以用的成品,想着拿出来并简单聊聊。

 决定先做一个音乐播放器。以下是我参考的文章,成品也是在这位作者的基础上改来的。

纯HTML+CSS+JS制作音乐播放器(附源码)_html音乐播放器代码-CSDN博客

 界面设计


功能预览

1. 播放模式

  • 列表循环
  • 单曲循环
  • 随机播放

2. 加载行为

  • 分页加载(也许?)
  • 加载视觉效果
  • 从上次退出时的位置播放

3. 用户交互

  • 键盘操作
  • 滚动列表更新
  • 搜索
  • 歌词(5.30更新)

具体实现

因为算是初学,写得不算好,请见谅。

注意,因为内容很多,在这里只会展示部分,如需源码请见文末. 同时,最新内容也仅在源码处展示

页面布局

HTML

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta content="IE=edge" http-equiv="X-UA-Compatible">
    <meta content="width=device-width, initial-scale=1.0" name="viewport">
    <link rel="shortcut icon" href="/static/img/ico.webp">
    <link rel="stylesheet" href="/static/css/audio_player_5_6.css">
    <title>Audio Player</title>
</head>
<body id="body">
<div class="base-alert-box"></div>
<div class="upper-container">
    <!-- 音乐介绍 -->
    <div class="introduction-container">
        <div id="text-container" class="text-container">
            <div id="music-title">音乐</div>
            <div class="author-container">作曲家:
                <span id="author-name">未知</span>
            </div>
            <div class="author-container">专辑:
                <span id="album-name">未知</span>
            </div>
        </div>
    </div>
</div>

<!-- 音乐播放器主要内容 -->
    <div class="audio-box">
        <div class="audio-container">
            <audio id="audioTag"></audio>
            <!-- 进度条 -->
            <div class="a-progress">
                <div class="pgs-total" id="progress-total">
                    <div class="pgs-loading" id="progress-loading"></div>
                    <div class="pgs-play" id="progress" style="width: 0;"></div>
                </div>
            </div>
            <!-- 下排控制按钮 -->
            <div class="a-controls">
                <!-- 播放时长 -->
                <div class="time-container">
                    <span class="played-time" id="playedTime">00:00</span>&nbsp;/&nbsp;
                    <span class="audio-time" id="audioTime">00:00</span>
                </div>
                <!-- 中间按钮 -->
                <div class="center-button-container">
                    <!-- 播放模式 -->
                    <div id="playMode" class="center-icon mode"></div>
                    <!-- 上一首 -->
                    <div id="skipForward" class="center-icon s-right"></div>
                    <!-- 暂停按钮 -->
                    <div id="playPause" class="icon-play"></div>
                    <!-- 下一首 -->
                    <div id="skipBackward" class="center-icon s-left"></div>
                    <!-- 音量调节 -->
                    <div id="volume" class="center-icon volume"></div>
                    <!-- 音量悬浮窗 -->
                    <label for="volume-toggle"></label>
                    <input type="range" id="volume-toggle" name="change" value="50" min="0" max="100" step="1">
                </div>
                <!-- 后部按钮 -->
                <div class="bottom-button-container">
                    <!-- 列表 -->
                    <div id="list" class="bottom-icon list"></div>
                </div>
            </div>
        </div>
    </div>

    <!-- 音乐列表 -->
    <div class="close-list" id="close-list"></div>
    <div class="music-list" id="music-list">
        <div class="music-list-container">
            <div class="music-list-title">播放队列</div>
            <hr class="line">
            <ul class="all-list" id="all-list"></ul>
            <img id="loading" src="/static/img/svg/loading.svg" width="20" alt="">
        </div>
    </div>
</body>
<script type="text/javascript" src="/static/js/audio_player/audio_player_var_5_11.js"></script>
<script type="text/javascript" src="/static/js/audio_player/audio_player_data-5_11.js"></script>
<script type="text/javascript" src="/static/js/audio_player/audio_player_listener_5_11.js"></script>
</html>

CSS

body {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
    background-repeat: no-repeat;
    backdrop-filter: blur(40px);
    transition: 200ms;

    /*CSS3去除手机浏览器按钮点击出现的高亮框*/
    -webkit-tap-highlight-color: transparent;
    -webkit-backdrop-filter: blur(40px);
}

html,
body {
    height: 100%;
    background-size: cover;
    overflow: hidden;
}

.upper-container {
    width: 80%;
    height: 80%;
}

.introduction-container {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    float: left;
}

.text-container {
    width: 80%;
    transition: 200ms;
}

.scroll-wrap {
    max-width: 100%;
}

.scroll-item {
    animation: scroll linear 6s alternate infinite;
}

#music-title {
    color: rgba(255, 255, 255, .888);
    font-size: 4rem;
    line-height: 6rem;
}

.author-container {
    color: rgb(171, 171, 171);
    font-size: medium;
}

/* 歌词 */
.lyric-title {
    position: absolute;
    top: 0;
    mix-blend-mode: difference;
    color: white;
    pointer-events: none;
    transition: 200ms;
}

.lyric-box {
    display: none;
    position: absolute;
    top: 10%;
    width: 90%;
    height: 70%;

    overflow: hidden;
    font-size: 20px;
    color: white;
    pointer-events: none;
    transition: 250ms ease-out;
}

/* 直接使用class定位到ul似乎会导致列表样式概率消失 */
.lyric-box ul {
    padding: 0;
    position: relative;
    top: 40%;
    list-style: none;
    text-align: center;
    user-select: none;
    -webkit-user-select: none;
    transition-duration: 600ms;
}

.lyric-box ul li {
    height: 25px;
    line-height: 25px;
    margin-top: 25px;
    transition: 200ms;
    opacity: .4;
}

.lyric-box ul li span {
    cursor: pointer;
}

.lyric-box ul li span span {
    font-size: 12px;
    pointer-events: none;
}

.lyric-box ul .highlight-line {
    color: #17E883;
    opacity: 1;
}

.lyric-box ul .near-line {
    opacity: 1;
}

/* 歌词调整 */

.lyric-calibration {
    position: absolute;
    display: flex;
    left: 0;
    top: 40%;
    flex-direction: column;
    z-index: 10;
}

.lyric-calibration img {
    margin: 5px 0;
    cursor: pointer;
    opacity: .4;
}

.lyric-calibration img:hover {
    opacity: .6;
}

.lyric-calibration img:active {
    opacity: 1;
}

#lyric-offset {
    text-align: center;
    font-size: 12px;
}

/* 播放器样式设置*/
.audio-box {
    width: 100%;
    height: 20%;
    display: flex;
    align-items: center;
    justify-content: center;
    /* 设置超出部分隐藏,方便改变图标颜色 */
    overflow: hidden;
}

.audio-container {
    width: 90%;
}

/* 进度条样式 */
.a-progress {
    width: 100%;
    color: #42b680;
    background-color: transparent;
    border-radius: 10px;
    margin-bottom: 10px;
    cursor: pointer;
}

.pgs-total {
    width: 100%;
    height: 16px;
    background-color: transparent;
    border-radius: 10px;
    position: relative;
    touch-action: none;
}

.a-progress .pgs-total:after,
.a-progress .pgs-total .pgs-loading,
.a-progress .pgs-total .pgs-play:before,
.a-progress .pgs-total .pgs-play:after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 7px;
    bottom: 7px;
}

.a-progress .pgs-total:after {
    background-color: #bbbbbb;
    border-radius: 10px;
    z-index: -1;
}

.a-progress .pgs-total .pgs-loading {
    display: none;
    box-shadow: #1affb2 0 5px 5px;
    animation: loadingProgressBar 1200ms ease-out alternate infinite;
}

.a-progress .pgs-total .pgs-play:before {
    background-color: #42b680;
    border-radius: 10px;
    z-index: 1;
}

.a-progress .pgs-total .pgs-play {
    height: 100%;
    position: relative;
    border-radius: 10px;
}

.a-progress .pgs-total .pgs-play:after {
    top: 4px;
    left: 100%;
    height: 8px;
    width: 8px;
    border-radius: 50%;
    background-color: #FFFFFFFF;
}

/* 下排控制按钮样式*/
.a-controls {
    -webkit-user-select: none;
    user-select: none;
    width: 100%;
}

/* 时间样式 */
.time-container {
    float: left;
    color: white;
    font-weight: 300;
    line-height: 50px;
    white-space: nowrap;
}

.played-time {
    left: 15px;
    text-align: left;
}

.audio-time {
    right: 15px;
    text-align: right;
}

/* 中间部分按钮 */
.center-button-container {
    width: 35%;
    float: left;
    display: flex;
    align-items: center;
    justify-content: center;
}

.center-button-container > * {
    cursor: pointer;
}

.center-icon {
    float: left;
    min-width: 30px;
    min-height: 30px;
    margin: 0 10px 0 10px;
    outline: none;
}

.center-icon:hover {
    opacity: .7;
}

.center-icon:active {
    opacity: .9;
}

.icon-pause, .icon-play, .s-left, .lyric, .mode, .s-right, .volume {
    background-size: 100% 100%;
    background-repeat: no-repeat;
}

.s-left {
    background-image: url("/static/img/audio/ico/next.svg");
}

.icon-play {
    float: left;
    min-width: 50px;
    min-height: 50px;
    background-image: url("/static/img/audio/ico/play.svg");
}

.icon-pause {
    float: left;
    min-width: 50px;
    min-height: 50px;
    background-image: url("/static/img/audio/ico/pause.svg");
}

.lyric {
    background-image: url("/static/img/audio/ico/lyric.svg");
}

.s-right {
    background-image: url("/static/img/audio/ico/per.svg");
}

.volume {
    background-image: url("/static/img/audio/ico/volume.svg");
}

/* 尾部按钮 */
.bottom-button-container {
    float: left;
    margin: 15px 0;
    pointer-events: none;
    width: 30%;
}

.bottom-icon {
    float: right;
    width: 20px;
    height: 20px;
    cursor: pointer;
    pointer-events: initial;
}

.bottom-icon:hover {
    opacity: .7;
}

.bottom-icon:active {
    opacity: .6;
}

.list {
    background-image: url("/static/img/audio/ico/list.svg");
    background-size: cover;
}

/* 音乐列表 */
.close-list {
    display: none;
    position: fixed;
    height: 100%;
    left: 0;
    top: 0;
    z-index: 100;
}

.music-list {
    display: none;
    position: fixed;
    height: 100%;
    right: 0;
    top: 0;
    background-color: rgba(153, 153, 153, .6);
}

.music-list-container {
    width: 100%;
}

.search {
    position: absolute;
    padding: 10px;
    right: 0;
    width: 20px;
    cursor: pointer;
}

.search:hover {
    opacity: .7;
}

.search:active {
    opacity: .5;
}

.search-bar {
    position: fixed;
    top: 20px;
    left: 100%;
    width: 0;
    height: 0;

    font-size: 15px;
    border-radius: 4px;
    background-color: rgba(255, 255, 255, .3);
    backdrop-filter: blur(8px);
    border: none;

    transition: 200ms ease-out;
}

.music-list-title {
    text-align: center;
    color: white;
    font-size: 2rem;
    font-weight: 300;
    padding: 10% 15% 0;
}

.line {
    height: 1px;
    width: 90%;
    border: none;
    border-top: 1px dashed #3d3d3d;
}

.all-list {
    display: flex;
    flex-direction: column;
    align-items: center;

    padding: 0;
    max-height: 80%;

    overflow-y: scroll;
    scrollbar-width: none;
    list-style-type: none;
}

::-webkit-scrollbar {
    display: none;
}

.all-list li {
    min-width: 80%;
    max-width: 80%;
    min-height: 50px;

    color: white;
    font-weight: 300;
    margin-top: 10px;
    padding: 0 5px 0 5px;

    overflow: hidden;
    white-space: nowrap;

    transition: 150ms;
}

.all-list-li-activate,
.all-list li:hover {
    min-width: 90%;
    background-color: rgb(150, 182, 156);
    cursor: pointer;
}

.list-card-show {
    animation: showAni;
    animation-duration: 1s;
    animation-fill-mode: forwards;
    -webkit-animation-fill-mode: forwards;
}

.list-card-hide {
    animation: hideAni;
    animation-duration: 1s;
    animation-fill-mode: forwards;
    -webkit-animation-fill-mode: forwards;
}

@media (min-width: 767px) {
    .lyric-box ul .highlight-line {
        font-size: 25px;
    }

    .time-container {
        width: 35%;
    }

    .close-list {
        width: 70%;
    }

    .music-list {
        width: 30%;
    }
}

@media (max-width: 767px) {
    .lyric-box {
        font-size: 15px;
    }

    .lyric-box ul .highlight-line {
        font-size: 20px;
    }

    .time-container {
        width: 25%;
        visibility: hidden;
    }

    .played-time, .audio-time {
        visibility: initial;
        position: absolute;
        bottom: 14%;
    }

    .close-list {
        width: 25%;
    }

    .music-list {
        width: 75%;
    }

    #volume, #volume-toggle, #lyric {
        display: none;
    }
}

@keyframes hideAni {
    from {
        transform: translateX(0%);
    }

    to {
        transform: translateX(100%);
    }
}

@keyframes showAni {
    from {
        transform: translateX(100%);
    }

    to {
        transform: translateX(0%);
    }
}

@keyframes rotateAni {
    from {
        transform: rotate(0deg);
    }

    to {
        transform: rotate(360deg);
    }
}

@keyframes loadingProgressBar {
    0% {
        opacity: 0;
    }
    100% {
        opacity: 1;
    }
}

/*横条样式*/
#volume-toggle {
    width: 150px;
    min-width: 50px;
    height: 5px;
    appearance: none;
    background-color: #dcdcdc;
    outline: none;
    overflow: hidden;
    border-radius: 15px;
    box-shadow: inset 0 0 5px rgb(91, 91, 91);
}

/*拖动块的样式*/
#volume-toggle::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 1px;
    height: 5px;
    border-radius: 50%;
    background: #42b680;
    cursor: pointer;
    border: 4px solid #33333300;
    box-shadow: -407px 0 0 400px #42b680;
}

#loading {
    display: none;
    margin: 10px 50% 0 50%;
    animation: rotateAni 1s infinite;
}

#audio-player {
    display: none;
}

JS (最新的设计请查看GitHub)

更新音乐列表数据

从后端获取到的音乐列表将被储存到一个对象中,不知道多少条会出现明显的性能问题。对于内存占用,在一百条时程序共使用2.1MB左右。

然后,所有关于静态资源的地址都需要根据自己的项目地址更改。

/**
 * 从后台获取音乐列表;
 *
 * 注意,不要随意调用此函数, 您应该尽可能使用switchAudio
 * @param {IUpdateData,{}} opts
 * */
async function updateData(opts = {}) {
    const defaultOpts = {
        init: false,
        newIndex: 0,
        ...opts
    }

    // 检查
    if ((!defaultOpts.init && defaultOpts.newIndex >= MaxAudioCount) || defaultOpts.newIndex < 0) {
        AudioIndex = POST_DATA['audio_index'];
        throw RangeError('超出最大获取范围');
    }

    // 发送的信息
    POST_DATA['audio_index'] = defaultOpts.newIndex;
    POST_DATA['search_string'] = v.SearchInputBar.value.toString().trim();

    // 显示加载图标
    v.LoadingIco.style.display = 'block';

    // 后台获取
    const response = await baseFetch('/audio/audio_lists', {
        body: JSON.stringify(POST_DATA)
    });
    const json = await response.json();

    if (!json) {
        POST_DATA['audio_index'] = AudioIndex;
        throw Error('Fetch fail');
    }

    // 隐藏加载图标
    setTimeout(() => v.LoadingIco.style.display = 'none', 2000);

    // 更新所有全局信息, 并启动后续任务
    if (json['status'] !== 1009) {
        createAlert(json['msg'], 'danger');
        throw Error('Error fetching data');
    }

    Object.assign(AudioObject, json['audio_dict']);

    MaxAudioCount = Number(json['item_counts']);

    if (AudioIndexRange) {
        const tempArray = Object.keys(AudioObject);
        AudioIndexRange = tempArray.length === MaxAudioCount ? null : [Number(tempArray[0]), Number(tempArray.pop())];
        if (AudioIndex >= MaxAudioCount) AudioIndex = AudioIndexRange[1];
    }

    createChildLi(AudioObject).catch();
}

展示列表更新

/**
 * 创建音乐列表
 * @param {Object} obj 标准音频列表格式
 * */
async function createChildLi(obj) {
    if (v.ListParentUl.children.length === MaxAudioCount) return;

    if (isEmpty(obj)) {
        v.ListParentUl.textContent = '无结果';
        return;
    }

    const frag = document.createDocumentFragment();

    const paragraph = (value) => {
        const p = document.createElement('p');
        p.textContent = `${value[0]} - ${value[1]} - ${value[2]}.${value[3]}`;
        return p;
    }

    const entries = Object.entries(obj);
    const len = entries.length;
    let index = 0;

    const updateBatch = () => {
        // 每次更新20个元素
        const batchSize = 20;
        for (let i = 0; i < batchSize && index < len; i++, index++) {
            const [key, value] = entries[index];
            const liElement = document.createElement('li');
            liElement.setAttribute('id', `li-${key}`);
            liElement.appendChild(paragraph(value));
            frag.appendChild(liElement);
        }

        if (index < len) {
            requestAnimationFrame(updateBatch);
            return;
        }
        v.ListParentUl.textContent = '';
        v.ListParentUl.appendChild(frag);
        highlightChosenSelection(false);
    }
    requestAnimationFrame(updateBatch);
}

初始化音乐及展示信息

在这里,背景图片以及音乐地址,音乐信息将被加载

// 加载音乐数据
function initAudio() {
    // 本地保存当前Audio index
    localStorage.setItem('audio_index', AudioIndex.toString());

    // 加载图标
    v.ProgressLoading.style.display = 'block';

    // 设置背景图片
    NewImgUrl = `/static/img/audio/webp/audio-${ImgNum}.webp`;
    ImgNum = (ImgNum + 1) % AllImgCount;

    // 预加载图片
    TempImgEle.src = NewImgUrl;

    // 设置音频信息
    ;[v.Author.textContent, v.AudioTitle.textContent, v.Album.textContent] = [...AudioObject[AudioIndex]];
    v.LyricTitle.textContent = v.AudioTitle.textContent;

    v.AudioTitle.removeAttribute('class');
    if (v.AudioTitle.clientWidth >= v.TextContainer.clientWidth) v.AudioTitle.className = 'scroll-item';

    // 更改URL
    const href = new URL(window.location.href);
    const params = new URLSearchParams(href.search);
    params.set('audio_index', AudioIndex.toString());
    params.set('search', v.SearchInputBar.value.toString());
    href.search = params.toString();
    history.replaceState(null, '', href.href);

    // 设置音乐, 并在加载后播放
    v.AudioEle.src = `/audio/play/${AudioObject[AudioIndex][4]}`;
    v.AudioEle.load();
}

滚动列表更新

移动端和桌面端方法

export const slideToUpdateFn = debounce(() => {
    if (!AudioIndexRange) return;

    const scrollTop = v.ListParentUl.scrollTop;
    const clientHeight = v.ListParentUl.offsetHeight;
    const scrollHeight = v.ListParentUl.scrollHeight;

    // 判断滚动方向
    const directionDown = ScrollUConfig.beforeScrollTop <= scrollTop;

    ScrollUConfig.beforeScrollTop = scrollTop;

    const [first, last] = AudioIndexRange;

    const shouldUpdateDown = directionDown &&
        (scrollTop + clientHeight + ScrollUConfig.threshold >= scrollHeight) &&
        (last + 1 < MaxAudioCount);

    const shouldUpdateUp = scrollTop <= ScrollUConfig.threshold && first - 1 >= 0;

    if (shouldUpdateDown) switchAudio(last + 1, {refresh: false, scroll: false});
    else if (shouldUpdateUp) switchAudio(first - 1, {refresh: false, scroll: false});
}, 100);
export const wheelingToUpdateFn = debounce((event) => {
    if (!AudioIndexRange) return;

    const scrollTop = v.ListParentUl.scrollTop;
    const clientHeight = v.ListParentUl.offsetHeight;
    const scrollHeight = v.ListParentUl.scrollHeight;

    const directionDown = event.deltaY > 0;

    const [first, last] = AudioIndexRange;

    const shouldUpdateDown = directionDown && last + 1 < MaxAudioCount && scrollTop + clientHeight + 20 >= scrollHeight;
    const shouldUpdateUp = !directionDown && first - 1 >= 0 && scrollTop === 0;

    if (shouldUpdateDown) switchAudio(last + 1, {refresh: false, scroll: false});
    else if (shouldUpdateUp) switchAudio(first - 1, {refresh: false, scroll: false});
}, 200);

下载链接

全部代码

github: AudioPlayer源码

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现HTML CSS JS原神音乐会,我们可以通过以下步骤完成。 首先,我们需要创建一个HTML文件作为网页的主要结构。在HTML文件中,我们可以使用div元素来创建不同的节目列表和播放器。 接下来,我们可以使用CSS来美化我们的页面。我们可以使用CSS选择器来选择特定的元素,并为其添加样式,例如背景颜色、字体、边框等。我们还可以使用CSS动画来增加交互效果,例如当鼠标悬停在音乐链接上时,呈现渐变颜色或是文字放大的效果。 在HTML文件中,我们需要在head标签里引入JavaScript文件。接下来,我们可以使用JavaScript来处理用户的交互和音乐播放功能。 对于音乐播放功能,我们可以使用原生HTML5音频标签来加载和播放音乐。例如,我们可以创建一个音频标签,并为其添加src属性,指定要播放的音乐文件。我们还可以使用JavaScript来控制音频标签的播放和暂停功能,并通过添加事件监听器来实现自动播放或播放结束后自动切换到下一曲目。 为了增加交互性,我们可以使用JavaScript和DOM (Document Object Model)来创建按钮,并为其添加点击事件,例如上一曲、下一曲和音量控制按钮。当点击按钮时,我们可以使用JavaScript来控制音频标签的相关属性和方法。 最后,为了让我们的页面更具有吸引力,我们可以通过添加一些原神的主题元素,例如游戏角色的图片和背景音乐,来改变整体的视觉效果。 通过以上的步骤,我们可以实现一个HTML CSS JS原神音乐会,使用户能够浏览和播放不同的原神音乐。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值