Vue 封装一个带渐变音柱条的音频控制组件

本文介绍如何使用Vue.js封装一个带渐变音柱条的音频控制组件。通过CSS的background-position和linear-gradient实现渐变效果,结合ElementUI的slider组件自定义样式。同时,为满足动态控制显示和隐藏的需求,利用v-if和v-show指令,以及自定义属性来实现这一功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

效果图

在这里插入图片描述

实现

左侧渐变条通过 CSS 实现,利用 background 属性进行背景定位结合 linear-gradient 线性渐变函数实现
右侧滑块引入 ElementUI 的 el-slider 组件,自定义样式,其中 marks 展开标记通过定位修改其默认位置达到刻度值的效果
封装成组件 VolumeBar.vue

这时候产品经理又说了,每个音频组件需要动态地控制显示和隐藏,如何在动刀最小的前提下满足它呢,总不至于通过 js 给一个个组件单独绑定事件去控制吧。这里用到了伪元素结合 :hover 伪类,当鼠标悬浮到每个组件上时显示伪元素样式,并通过 content: attr(data-hide) 动态指定其要显示的文本,需要在对应的元素上添加自定义属性,这里是 data-hide

代码如下:

<template>
    <div v-if="!isHide || isEdit" :class="{ 'hide-wrapper': isEdit }" :data-hide="isHide ? '显示' : '隐藏'"
        @click="showOrHide">

        <div :class="['volume-panel', isHide ? 'hide-pannel' : '', isEdit ? 'prevent' : '']">
            <div class="label">{{ name }}</div>
            <div class="volume">
                <!-- 渐变音柱 -->
                <div class="volume-bar">
                    <div class="volume-bar-inner top-bar" :style="{ height: `${100 - sliderValue}%` }"></div>
                    <div class="volume-bar-inner bottom-bar" :style="{ height: `${sliderValue}%` }"></div>
                </div>
                <!-- 音量控制滑块 -->
                <el-slider v-model="sliderValue" @input="sliderValueChange" vertical height="200px" :show-tooltip="false"
                    :marks="marks"></el-slider>
            </div>
            <el-input-number v-model="volumeInput" @change="volumeInputChange" :min="min" :max="max"></el-input-number>
        </div>

    </div>
</template>

<script>
export default {
    props: {
        name: String,
        min: Number,
        max: Number,
        isEdit: Boolean,
    },
    data() {
        return {
            sliderValue: 0,
            volumeInput: 0,
            isHide: false,
        }
    },
    computed: {
        marks() {
            let obj = {}
            let step = (this.max - this.min) / 10
            for (let i = 0; i <= 10; i++) {
                let value = this.min
                value += step * i
                obj[i * 10] = Math.round(value).toString()
            }
            return obj
        },
    },
    methods: {
        showOrHide() {
            if (!this.isEdit) return
            this.isHide = !this.isHide
        },

        transferToSliderValue(volumeInput) {
            this.sliderValue = Math.abs(this.min - volumeInput) / (this.max - this.min) * 100
        },

        transferToVolumeInput(sliderValue) {
            this.volumeInput = Math.round(this.min + ((this.max - this.min) / 100) * sliderValue)
        },

        sliderValueChange() {
            this.transferToVolumeInput(this.sliderValue)
        },

        volumeInputChange() {
            this.transferToSliderValue(this.volumeInput)
        }
    }
}
</script>

<style lang="scss" scoped>
.hide-wrapper {
    position: relative;

    &:hover {
        border: 1px solid #0C87F0;

        &::before {
            content: attr(data-hide);
            position: absolute;
            z-index: 1;
            height: 70px;
            width: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            background-color: rgba(12, 135, 240, .8);
        }
    }
}

.volume-panel {
    width: 108px;
    background: url('@/assets/images/video/bg_sound.png') no-repeat center / 100% 100%;
    display: flex;
    flex-direction: column;
    align-items: center;

    .label {
        line-height: 40px;
        text-align: center;
    }

    .volume {
        margin-bottom: 10px;
        opacity: .95;
        display: flex;

        .volume-bar {
            transform: translateX(-10px);
            width: 10px;
            background: linear-gradient(180deg, #E54948 2%, #FFB637 43%, #0BD4FF 97%);
            position: relative;

            .volume-bar-inner {
                position: absolute;
                left: 0;
                width: 100%;
                height: 50%;
            }

            .bottom-bar {
                bottom: 0;
                height: 70%;
                background: linear-gradient(180deg, #333C49 50%, rgba(0, 0, 0, .01) 50%) bottom;
                background-size: 100% 4px;
            }

            .top-bar {
                height: 30%;
                top: 0;
                background: linear-gradient(0deg, #333C49 50%, #5D6878 50%) top;
                background-size: 100% 4px;
            }
        }
    }
}

.hide-pannel {
    background: rgb(40, 47, 60);
}

.prevent {
    pointer-events: none;
}

:deep(.el-slider) {
    .el-slider__runway {
        background-color: #1D2128;
        border-top: 4px solid #1D2128;
        border-bottom: 4px solid #1D2128;

        .el-slider__stop {
            width: 30px;
            height: 1px;
            background-color: #8695A6;
            z-index: -1;
            left: -13px;
        }

        .el-slider__marks-text {
            left: 20px;
            font-size: 10px;
            color: #657181;
        }
    }

    .el-slider__bar {
        background-image: linear-gradient(160deg, #2673DC 18%, #62B6FF 90%);
    }

    .el-slider__button {
        width: 20px;
        height: 26px;
        border: none;
        border-radius: 0;
        background: url('@/assets/images/video/btn_sound_hand.png') center / 100% 100%;
    }
}

:deep(.el-input-number) {
    width: 100px;
    margin: 8px;
    line-height: 30px;

    .el-input__inner {
        padding: 0;
        height: 32px;
        background-color: #454f61;
        border: 1px solid #303a48;
        color: #fff;
    }

    .el-input-number__decrease,
    .el-input-number__increase {
        width: 30px;
        color: #fff;
        background-color: #454f61;
    }

    .el-input-number__decrease {
        border-right: 1px solid #303a48;
    }

    .el-input-number__increase {
        border-left: 1px solid #303a48;
    }
}
</style>

在父组件中引入:

<template>
  <div id="app">
    <el-button type="text" @click="isEdit = !isEdit">{{ isEdit ? '退出编辑' : '编辑' }}</el-button>
    <div class="volumes">
      <volume-bar v-for="(item, index) in volumes" :key="index" :isEdit="isEdit" :name="item.name" :min="0" :max="100" />
    </div>
  </div>
</template>

<script>
import VolumeBar from '@/components/VolumeBar.vue'
export default {
  name: 'App',
  components: {
    VolumeBar
  },
  data() {
    return {
      volumes: [
        { name: 'OUT1' },
        { name: 'OUT2' },
        { name: 'OUT3' },
      ],
      isEdit: false
    }
  }
}
</script>

<style>
#app {
  width: 100%;
  height: 100%;
  padding: 20px;
  background-color: #242a35;
  color: #fff;
}
.volumes {
  display: flex;
  gap: 20px;
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值