基于华为云主机 CodeArts IDE 与 JavaScript 的动态文字弹幕网页设计

本案例由开发者:华为2024年第三批次协同育人项目-山东科技大学-崔宾阁老师提供

1 概述

1.1 案例介绍

随着动态交互需求的日益增长,动态文字弹幕作为一种典型的交互功能,已被广泛应用于视频播放、直播等众多领域。华为开发者空间云主机平台为前端开发提供了稳定高效的开发环境,使开发者能够在云端快速搭建应用。

本案例依托华为开发者空间提供的云主机与CodeArts IDE,结合JavaScript技术,开发了一个功能丰富的动态文字弹幕系统。用户可以在输入框中输入文本,然后点击“发送弹幕”按钮发送弹幕。用户还可以通过控制面板自定义弹幕的颜色、速度和减速效果,增强弹幕的视觉效果和交互体验。此外,系统还提供了历史记录功能,用户可以查看和重新发送之前发送过的弹幕内容。

通过本案例,开发者将深入学习HTML语义化标签的使用、CSS的响应式设计、动画渲染技术及JavaScript的DOM操作、事件监听等技术。同时,开发者将熟悉华为云主机平台及CodeArts IDE开发环境,提升项目实战能力,为后续更复杂的Web应用开发打下坚实基础。

1.2 案例时间

本案例总时长预计30分钟。

1.3 案例流程

d555ae9fbe6549c79412ed969f439dd3

说明:

① 申请并登录华为开发者空间—云主机,打开CodeArts IDE创建工程;

② 在云主机CodeArts IDE for Java中编写案例项目代码;

③ 通过Firefox启动案例项目的入口文件;

1.4 资源总览

本案例预计花费总计0元。

资源名称规格单价(元)时长(分钟)
开发者空间—云主机鲲鹏通用计算增强型 kC2 | 4vCPUs | 8G | Ubuntu免费30
CodeArts IDECodeArts IDE for Java免费30


2 操作步骤

2.1 配置开发者空间—云主机

本案例中,使用华为开发者空间所提供的云主机平台以及CodeArts IDE+ JavaScript开发工具,完成动态文字弹幕案例的开发工作。点击链接( https://support.developer.huaweicloud.com/doc/development/resource-tools/zh-cn_topic_0000002367559525-0000002367559525 )可跳转至免费领取云主机指南。

1. 在浏览器中输入华为云开发者空间网址:https://developer.huaweicloud.com/developerspace,进入华为云开发者空间页面。

在华为开发者空间页面点击“免费领取”,跳转到开发者空间页面,如未领取根据页面提示进行云主机领取。

2bd61587c5914f228951cd2782d78a82

2. 在开发者空间页面,点击左侧“工作台”按钮进入工作台页面,再点击“配置云主机”进行云主机的配置。

83c1d670d60247d88a26b15e1f7201f0

3. 在配置云主机窗口中自定义云主机名称,配置完毕后点击“安装”。

237c9910b83b4bf3929c563f8029289b

4. 安装完毕后点击“打开云主机”>“进入桌面”即可进入云主机。

deaf6f12d8cf4abe80815099da78d17b

5. 等待环境云主机下载镜像、安装系统、安装工具集,首次进入云主机大约需要3至5分钟。

efcc9ec3ffe34096bf7727a0e7063745

6. 环境准备完毕后,即可进入云主机,云主机桌面如下图所示。

96e5a1e481774d30948c1a5a874f1563

2.2 创建项目

CodeArts IDE是一个集成开发环境(IDE),兼具源代码编辑器的简易性和开发人员工具的强大功能,如代码补全和调试。它将精简的源代码编辑器与强大的开发者工具结合在一起。

1. 双击打开云主机桌面上的CodeArts IDE for Java(虽然IDE名称带有“Java”,但本案例用于编写JavaScript前端代码)。

6c25fef501234125995b4e9a1f62f828

2. 首次使用CodeArts IDE创建工程,可直接点击左侧栏目中的“新建工程”,项目名称为“DynamicTextBullet”(用户可自行取名),工程存放位置、构建系统及JDK直接使用默认配置,接着点击右下角蓝色的“创建”按钮。

1a254264ae8544d2adc8a6c84426c6f3

3. 在新建的“DynamicTextBullet”工程项目中,点击左上角新建HTML文件,输入:DynamicTextBullet.html,输入完毕后按下回车键,该HTML文件用于存放实现动态文字弹幕的代码,代码内容在后续文中进行介绍

63af6d5e7ebf44a2acf1b7904717b6e9

4. 代码编写完成后,鼠标右键单击“DynamicTextBullet.html”文件,选择“打开所在的文件夹”,找到动态文字弹幕项目在云主机中的位置。

f4ee501e9a8f4cb4bb4465e024cef68b

5. 在打开的项目文件夹中,鼠标右键单击编写好的网页代码文件,依次点击“打开方式”>“Firefox”,在火狐浏览器中查看动态文字弹幕页面。

613114e24a644353aa86f6989d2de34c

2.3 编写代码

本案例由一个项目文件组成,文件名为DynamicTextBullet.html,由HTML + CSS + JS组件构成,其功能是在网页中实现了一个动态文字弹幕系统。动态文字弹幕系统包含一个视频容器,其中弹幕以动态的方式从容器右侧进入并滚动至容器左侧移出视野。用户可以通过输入框发送弹幕,弹幕会以不同的速度和减速效果滚动,并且可以通过鼠标悬停来减速查看弹幕内容。同时,用户还可以通过控制面板自行设置输入弹幕的颜色、速度和减速效果。

动态文字弹幕网页参考代码如下(代码存放于DynamicTextBullet.html文件中):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>动态文字弹幕</title>
    <style>
        :root {
            --bg0: #0f0c1f;
            --bg1: #1a1142;
            --neonC: #00f9ff;
            --neonM: #ff00a0;
            --glass: rgba(255,255,255,.08);
        }
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif
        }
        body {
            min-height: 100vh;
            background: linear-gradient(135deg,var(--bg0),var(--bg1));
            color: #fff;
            padding: 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
        }
        .header {
            text-align: center;
            margin-bottom: 20px;
            width: 100%;
            max-width: 800px;
        }
            .header h1 {
                font-size: 2.5rem;
                margin-bottom: 10px;
                text-shadow: 0 0 10px var(--neonC);
            }
            .header p {
                font-size: 1.1rem;
                opacity: .9
            }
        /* === 视频容器 === */
        #video-container {
            position: relative;
            width: 100%;
            max-width: 800px;
            height: 450px;
            margin: 0 auto 20px;
            background: #000;
            border: 2px solid var(--neonC);
            border-radius: 12px;
            box-shadow: 0 0 20px var(--neonC);
            overflow: hidden;
        }
        #container {
            position: relative;
            width: 100%;
            height: 100%
        }
        /* === 弹幕 === */
        .bullet {
            position: absolute;
            padding: 8px 15px;
            border-radius: 25px;
            font-size: 20px;
            font-weight: bold;
            white-space: nowrap;
            cursor: pointer;
            transition: all .3s ease;
            box-shadow: 0 0 10px currentColor;
            color: #000 !important;
        }
            .bullet:hover {
                transform: scale(1.05);
                z-index: 10
            }
        .delete-text {
            margin-left: 10px;
            padding: 5px 10px;
            font-size: 14px;
            background: var(--neonM);
            color: #fff;
            border-radius: 15px;
            display: none;
        }
        .bullet:hover .delete-text {
            display: inline-block
        }
        /* === 输入区 === */
        .input-section {
            position: absolute;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            width: 90%;
            display: flex;
            background: var(--glass);
            border: 1px solid var(--neonC);
            border-radius: 50px;
            padding: 12px 20px;
            gap: 10px;
        }
            .input-section input {
                flex: 1;
                padding: 12px 20px;
                border: none;
                border-radius: 30px 0 0 30px;
                outline: none;
                background: #fff;
                color: #000;
            }
            .input-section button {
                padding: 12px 25px;
                border: none;
                border-radius: 0 30px 30px 0;
                background: linear-gradient(90deg,var(--neonC),var(--neonM));
                color: #fff;
                font-weight: bold;
                cursor: pointer;
                transition: .3s;
            }
                .input-section button:hover {
                    filter: brightness(1.2)
                }
        /* === 控制区 === */
        .controls {
            display: flex;
            flex-wrap: wrap;
            gap: 20px;
            width: 100%;
            max-width: 800px;
            background: var(--glass);
            border: 1px solid var(--neonM);
            border-radius: 15px;
            padding: 20px;
        }
        .control-group {
            flex: 1;
            min-width: 200px
        }
            .control-group h3 {
                margin-bottom: 8px
            }
        .color-picker {
            display: flex;
            gap: 8px;
            flex-wrap: wrap
        }
        .color-option {
            width: 30px;
            height: 30px;
            border-radius: 50%;
            cursor: pointer;
            border: 2px solid transparent;
            transition: .2s;
        }
            .color-option.active,
            .color-option:hover {
                transform: scale(1.15);
                border: 2px solid #fff
            }
        .slider-container {
            display: flex;
            align-items: center;
            gap: 10px
        }
            .slider-container input {
                flex: 1;
                -webkit-appearance: none;
                height: 6px;
                border-radius: 3px;
                background: var(--glass);
                outline: none
            }
                .slider-container input::-webkit-slider-thumb {
                    -webkit-appearance: none;
                    width: 18px;
                    height: 18px;
                    border-radius: 50%;
                    background: var(--neonM);
                    cursor: pointer
                }
        /* === 历史区 === */
        .history {
            width: 100%;
            max-width: 800px;
            background: var(--glass);
            border: 1px solid var(--neonC);
            border-radius: 15px;
            padding: 20px;
        }
            .history h2 {
                margin-bottom: 15px;
                text-align: center
            }
        .history-list {
            max-height: 150px;
            overflow-y: auto;
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
        }
        .history-item {
            background: rgba(255,255,255,.15);
            padding: 8px 15px;
            border-radius: 20px;
            cursor: pointer;
            transition: .2s;
        }
            .history-item:hover {
                background: rgba(255,255,255,.25);
                transform: translateY(-2px)
            }
        /* === 响应式 === */
        @media(max-width:600px) {
            .input-section {
                flex-direction: column;
                border-radius: 20px
            }
                .input-section input {
                    border-radius: 20px 20px 0 0;
                    margin-bottom: 10px
                }
                .input-section button {
                    border-radius: 0 0 20px 20px
                }
            .header h1 {
                font-size: 2rem
            }
        }
    </style>
</head>
<body>
<div class="header">
    <h1>动态文字弹幕</h1>
</div>
<div id="video-container">
    <div id="container"></div>
    <div class="input-section">
        <input type="text" id="messageInput" placeholder="输入弹幕内容...">
        <button id="sendButton">发送弹幕</button>
    </div>
</div>
<div class="controls">
    <div class="control-group">
        <h3>弹幕颜色</h3>
        <div class="color-picker" id="colorPicker">
            <div class="color-option active" style="background-color: #FF5252;" data-color="#FF5252"></div>
            <div class="color-option" style="background-color: #FFD740;" data-color="#FFD740"></div>
            <div class="color-option" style="background-color: #7C4DFF;" data-color="#7C4DFF"></div>
            <div class="color-option" style="background-color: #18FFFF;" data-color="#18FFFF"></div>
            <div class="color-option" style="background-color: #69F0AE;" data-color="#69F0AE"></div>
            <div class="color-option" style="background-color: #FFFFFF;" data-color="#FFFFFF"></div>
        </div>
    </div>
    <div class="control-group">
        <h3>弹幕速度</h3>
        <div class="slider-container">
            <input type="range" id="speedSlider" min="1" max="10" value="3">
            <span id="speedValue">3</span>
        </div>
    </div>
    <div class="control-group">
        <h3>减速效果</h3>
        <div class="slider-container">
            <input type="range" id="slowdownSlider" min="1" max="10" value="3">
            <span id="slowdownValue">3</span>
        </div>
    </div>
</div>
<div class="history">
    <h2>发送历史</h2>
    <div class="history-list" id="historyList"></div>
</div>
<script>
    // 获取DOM元素
    const container = document.getElementById('container');
    const messageInput = document.getElementById('messageInput');
    const sendButton = document.getElementById('sendButton');
    const colorPicker = document.getElementById('colorPicker');
    const speedSlider = document.getElementById('speedSlider');
    const speedValue = document.getElementById('speedValue');
    const slowdownSlider = document.getElementById('slowdownSlider');
    const slowdownValue = document.getElementById('slowdownValue');
    const historyList = document.getElementById('historyList');
    // 定义弹幕数据
    let data = [];
    let speed = 3;
    let slowdownFactor = 3; // 减速系数
    let interval = 800;
    let topArr = [];
    let index = 0;
    let selectedColor = '#FF5252';
    let history = [];
    // 初始化弹幕轨道
    for (let i = 0; i < 10; i++) {
        topArr.push(i * 40);
    }
    // 设置颜色选择器
    colorPicker.querySelectorAll('.color-option').forEach(option => {
        option.addEventListener('click', () => {
            colorPicker.querySelector('.active').classList.remove('active');
            option.classList.add('active');
            selectedColor = option.dataset.color;
        });
    });
    // 设置速度滑块
    speedSlider.addEventListener('input', () => {
        speed = parseInt(speedSlider.value);
        speedValue.textContent = speed;
    });
    // 设置减速滑块
    slowdownSlider.addEventListener('input', () => {
        slowdownFactor = parseInt(slowdownSlider.value);
        slowdownValue.textContent = slowdownFactor;
    });
    // 发送弹幕
    sendButton.addEventListener('click', sendDanmu);
    messageInput.addEventListener('keypress', function(event) {
        if (event.key === 'Enter') {
            sendDanmu();
        }
    });
    function sendDanmu() {
        const message = messageInput.value.trim();
        if (message) {
            data.push({ text: message, color: selectedColor });
            // 添加到历史记录
            addToHistory(message);
            messageInput.value = '';
            messageInput.focus();
        }
    }
    // 添加到历史记录
    function addToHistory(message) {
        history.push(message);
        if (history.length > 10) {
            history.shift();
        }
        updateHistoryList();
    }
    // 更新历史记录列表
    function updateHistoryList() {
        historyList.innerHTML = '';
        history.forEach((msg, index) => {
            const item = document.createElement('div');
            item.className = 'history-item';
            item.textContent = msg;
            item.addEventListener('click', () => {
                messageInput.value = msg;
                messageInput.focus();
            });
            historyList.appendChild(item);
        });
    }
    // 定时发送弹幕
    setInterval(function() {
        if (data.length > 0) {
            const bulletData = data[index];
            // 创建弹幕元素
            const bullet = document.createElement('div');
            bullet.className = 'bullet';
            bullet.style.color = 'white';
            bullet.style.backgroundColor = bulletData.color;
            bullet.textContent = bulletData.text;
            // 随机获取弹幕位置
            const top = topArr[Math.floor(Math.random() * topArr.length)];
            bullet.style.top = top + 'px';
            // 将弹幕元素添加到容器中
            container.appendChild(bullet);
            // 计算弹幕宽度
            const width = bullet.offsetWidth;
            // 定义弹幕起始位置
            let left = container.offsetWidth;
            bullet.style.left = left + 'px';
            // 定义弹幕移动定时器
            const bulletTimer = setInterval(function() {
                // 判断是否鼠标悬浮
                if (bullet.isHovered) {
                    left -= speed / slowdownFactor; // 应用减速系数
                } else {
                    left -= speed;
                }
                bullet.style.left = left + 'px';
                // 判断弹幕是否移出容器
                if (left < -width) {
                    // 移除弹幕元素
                    container.removeChild(bullet);
                    // 清除弹幕移动定时器
                    clearInterval(bulletTimer);
                }
            }, 10);
            // 保存定时器ID以便删除
            bullet.bulletTimer = bulletTimer;
            // 更新弹幕索引
            index = (index + 1) % data.length;
            // 为弹幕添加事件监听
            bullet.addEventListener('mouseenter', function() {
                this.isHovered = true;
                // 创建删除按钮
                if (!this.querySelector('.delete-text')) {
                    const deleteText = document.createElement('span');
                    deleteText.className = 'delete-text';
                    deleteText.textContent = '删除';
                    this.appendChild(deleteText);
                }
            });
            bullet.addEventListener('mouseleave', function() {
                this.isHovered = false;
                // 移除删除按钮
                const deleteText = this.querySelector('.delete-text');
                if (deleteText) {
                    this.removeChild(deleteText);
                }
            });
        }
    }, interval);
    // 删除弹幕
    container.addEventListener('click', function(event) {
        // 如果点击的是删除按钮
        if (event.target.classList.contains('delete-text')) {
            const bullet = event.target.parentElement;
            clearInterval(bullet.bulletTimer);
            container.removeChild(bullet);
        }
    });
    // 初始化一些示例弹幕
    setTimeout(() => {
        data.push({ text: '欢迎使用弹幕系统!', color: '#7C4DFF' });
        data.push({ text: '试试悬浮减速效果', color: '#FFD740' });
        data.push({ text: '鼠标悬停可减速查看', color: '#18FFFF' });
        data.push({ text: '点击删除按钮可移除弹幕', color: '#69F0AE' });
        data.push({ text: '可以调整弹幕速度和减速效果', color: '#FFFFFF' });
    }, 1000);
</script>
</body>
</html>

代码实现了一个动态文字弹幕网页,主要功能如下:

1. 页面总体布局

(1) 头部:显示动态文字弹幕的标题。

(2) 视频容器:包含弹幕显示区域和弹幕输入区域。                      

(3) 控制面板:包括颜色选择器、速度滑块组件和减速效果滑块组件,允许用户自定义弹幕的颜色、速度和减速效果。            

(4) 历史记录:显示用户发送的弹幕历史记录。

2. 功能实现

(1) 弹幕输入

① 用户可以在弹幕输入区域输入弹幕内容,并通过点击“发送弹幕”按钮或者按下回车键发送弹幕。② 用户可以通过点击颜色选择器中的不同颜色选项,改变输入弹幕的背景颜色。

(2) 弹幕显示

① 在弹幕显示区域,弹幕以动态的方式从容器右侧进入并滚动至容器左侧移出视野。

② 用户可以使用滑块组件来调整弹幕显示区域中弹幕的移动速度和减速效果。

(3) 弹幕删除

① 鼠标悬停在弹幕上时,弹幕的移动速度会减慢,并且会显示一个“删除”按钮,用户可以点击弹幕上的“删除”按钮来手动删除弹幕,从而保持弹幕显示区域的整洁。

(4) 历史记录

① 发送的弹幕内容会被保存在历史记录中,用户可以点击历史记录中的弹幕内容,将其重新加载到输入框中,方便再次发送。

3. 技术要点

(1) HTML结构:页面整体结构清晰,核心是视频容器和控制面板区域。视频容器由<div id="video-container">表示,包含弹幕显示区域和弹幕输入区域。控制面板由<div class="controls">表示,包括颜色选择器、速度和减速效果滑块组件。

(2) CSS布局:弹幕显示区域使用position: relativeoverflow: hidden确保弹幕在指定区域内滚动。每个弹幕项使用position: absolute动态定位,并通过transitionbox-shadow等属性来增强弹幕的视觉效果。

(3) DOM 操作使用document.createElement动态创建弹幕元素,然后通过定时器和style.left属性动态更新弹幕的位置,实现弹幕从右向左滚动的效果;当鼠标悬停在弹幕上时,使用document.createElement动态创建“删除”按钮;使用innerHTML清空历史记录列表,并使用document.createElementappendChild更新历史弹幕记录。

(4) 事件监听:为“发送弹幕”按钮添加点击事件,点击时调用sendDanmu函数发送弹幕,同时为输入框添加keypress事件监听,当用户按下回车键时调用sendDanmu函数发送弹幕;为颜色选择器的每个颜色选项添加点击事件,当点击某个选项时,移除其他选项的active类,并将active类添加到当前点击的选项。同时,将selectedColor变量更新为当前选项的颜色值;为速度和减速效果滑块添加input事件监听,当用户调整滑块时,更新speed变量和slowdownFactor变量为滑块的当前值。

2.4 展示动态文字弹幕网页

1. 动态文字弹幕网页展示

2577b84e60af4a8dab38d8dd161857f9

至此本次实验全部内容完成。

2.5 拓展实验

1. 增强交互性:比如增加更多弹幕特效(通过CSS动画和JavaScript动态修改弹幕的样式,例如渐变颜色、动态缩放、旋转等,使弹幕更加生动有趣)、增加弹幕发送音效(当点击“发送弹幕”按钮或者按下回车键时,播放对应音效)。

2. 动态文字弹幕功能扩展:比如增加弹幕点赞功能(在弹幕元素上添加“点赞”按钮,允许用户对认同或喜爱的弹幕进行点赞)、增加弹幕透明度、弹幕字体大小控制功能(允许用户调节弹幕的透明度和字体大小)。

3. 欢迎自由发挥


3 释放资源

3.1 关闭云主机

1. 首先点击云主机桌面上面的“关机”按钮,然后在弹出来的提示框点击“确定”按钮,关闭云主机。

cd9679902e974017953486d96046269b

2. 点击“确定”按钮后页面自动跳转到开发者空间页面,可以看到我的云主机显示“关机中”,说明云主机正在关机。

ac87da81611245ee90320d0d787872dd

3.2 检查资源

云主机关机后,等待3至5分钟,当我的云主机显示“已就绪”时,说明云主机成功关机,下次使用云主机只需点击“打开云主机”>“进入桌面”即可,可参考2.1小节。        

79055e9b468f42cb8496673774bc8ca9


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值