vue3+ts封装类似于微信消息的组件

组件代码如下:

html 复制代码
<template>
	<div
		:class="['voice-message', { sent: isSent, received: !isSent }]"
		:style="{ backgroundColor: backgroundColor }"
		@click="togglePlayback"
	>
		<!-- isSent为false在左侧,为true在右侧-->
		<!-- 语言条要按照语音时长显示不同的宽度,所以增加了一块宽度,发送者的时候,加在左侧,接收者的时候,加在右侧-->
		<div v-if="isSent" :style="`width:${(duration / 10) * 30}px`"></div>
		<span class="duration" v-if="isSent">{{ duration }}''&nbsp;</span>
		<div :class="['voice-icon', { 'sent-icon': isSent }]">
			<div :class="['small']" :style="smallStyle"></div>
			<div :class="['middle', { animate: isPlaying }]" :style="middleStyle"></div>
			<div :class="['large', { animate: isPlaying }]" :style="largeStyle"></div>
		</div>
		<span class="duration" :style="{ color: iconColor }" v-if="!isSent">{{ duration }}&nbsp;''</span>
		<div v-if="!isSent" :style="`width:${(duration / 10) * 30}px`"></div>
	</div>
</template>

<script setup lang="ts">
import { ref, computed, withDefaults, onBeforeUnmount } from "vue";

// 使用 withDefaults 提供默认值
const props = withDefaults(
	defineProps<{
		isSent?: boolean;
		iconColor?: string;
		backgroundColor?: string;
		smallSize?: number;
		middleSize?: number;
		largeSize?: number;
		duration?: number;
		audioSrc?: string;
	}>(),
	{
		isSent: false,
		iconColor: "#000000",
		backgroundColor: "",
		smallSize: 10,
		middleSize: 20,
		largeSize: 30,
		duration: 0,
		audioSrc: ""
	}
);

const isPlaying = ref(false);
let audio: HTMLAudioElement | null = null;

// 计算动态样式
const smallStyle = computed(() => ({
	color: props.iconColor,
	width: `${props.smallSize}px`,
	height: `${props.smallSize}px`,
	marginRight: -props.smallSize + "px"
}));

const middleStyle = computed(() => ({
	color: props.iconColor,
	width: `${props.middleSize}px`,
	height: `${props.middleSize}px`,
	marginRight: -props.middleSize + "px"
}));

const largeStyle = computed(() => ({
	color: props.iconColor,
	width: `${props.largeSize}px`,
	height: `${props.largeSize}px`,
	marginRight: "1px"
}));

// 切换播放状态的函数
const togglePlayback = () => {
	if (isPlaying.value) {
		pauseVoice();
	} else {
		playVoice(props.audioSrc || "");
	}
};

// 播放音频的函数
const playVoice = (voiceSrc: string) => {
	if (!voiceSrc) {
		console.error("音频源不能为空");
		return;
	}

	// 如果音频上下文不存在,则创建新的 HTMLAudioElement
	if (!audio) {
		audio = new Audio(voiceSrc);
	} else {
		audio.src = voiceSrc;
	}

	isPlaying.value = true;

	// 播放音频
	audio.play().catch(error => console.error("音频播放失败", error));

	// 监听播放结束事件
	audio.onended = () => {
		isPlaying.value = false;
	};
};

// 暂停音频的函数
const pauseVoice = () => {
	isPlaying.value = false;
	if (audio) {
		audio.pause();
	}
};

// 组件卸载时销毁音频上下文
onBeforeUnmount(() => {
	if (audio) {
		audio.pause();
		audio = null;
	}
});

defineExpose({
	pauseVoice
});
</script>

<style scoped>
.voice-message {
	display: inline-flex;
	align-items: center;
	cursor: pointer;
	border-radius: 10px;
	padding: 4px 12px;
}

.voice-message.sent {
	justify-content: flex-end;
}

.voice-message.received {
	justify-content: flex-start;
}

.voice-icon {
	display: flex;
	align-items: center;
}

.voice-icon.sent-icon {
	transform: rotate(180deg);
}

.small,
.middle,
.large {
	border-style: solid;
	border-top-color: transparent;
	border-left-color: transparent;
	border-bottom-color: transparent;
	border-radius: 50%;
	box-sizing: border-box;
	vertical-align: middle;
	display: inline-block;
	background-color: transparent; /* 默认背景颜色为透明 */
}

.middle.animate {
	animation: show2 3s ease-in-out infinite;
}

.large.animate {
	animation: show3 3s ease-in-out infinite;
}

@keyframes show2 {
	0% {
		opacity: 0;
	}
	30% {
		opacity: 1;
	}
	100% {
		opacity: 0;
	}
}

@keyframes show3 {
	0% {
		opacity: 0;
	}
	60% {
		opacity: 1;
	}
	100% {
		opacity: 0;
	}
}

.duration {
	margin-left: 8px;
	font-size: 20px;
	color: #ffffff;
	font-weight: 400;
}
</style>

使用时:

html 复制代码
<VoicePlayback
	:isSent="false"
	iconColor="#ffffff"
	backgroundColor="rgba(255 255 255 / 20%)"
	:smallSize="5"
	:middleSize="16"
	:largeSize="28"
	:duration="30"
	audioSrc="http://music.163.com/song/media/outer/url?id=447925558.mp3"
/>
相关推荐
VX:Fegn089510 小时前
计算机毕业设计|基于springboot + vue校园社团管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·课程设计
Fortunate Chen10 小时前
类与对象(下)
java·javascript·jvm
ChangYan.11 小时前
直接下载源码但是执行npm run compile后报错
前端·npm·node.js
skywalk816311 小时前
在 FreeBSD 上可以使用的虚拟主机(Web‑Hosting)面板
前端·主机·webmin
ohyeah12 小时前
深入理解 React 中的 useRef:不只是获取 DOM 元素
前端·react.js
MoXinXueWEB12 小时前
前端页面获取不到url上参数值
前端
低保和光头哪个先来12 小时前
场景6:对浏览器内核的理解
开发语言·前端·javascript·vue.js·前端框架
想要一只奶牛猫12 小时前
Spring Web MVC(三)
前端·spring·mvc
+VX:Fegn089512 小时前
计算机毕业设计|基于springboot + vueOA工程项目管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
奋飛12 小时前
微前端系列:核心概念、价值与应用场景
前端·微前端·micro·mfe·什么是微前端