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"
/>
相关推荐
不cong明的亚子3 分钟前
web live多屏互动平台
javascript·经验分享·音视频
陆枫Larry10 分钟前
Astro 是什么
前端
是立不是利2 小时前
写给设计师的 CSS 博客美学指南
前端·css
dsyyyyy11012 小时前
JS复习,难点,易错点总结
开发语言·javascript·ecmascript
默_笙2 小时前
🏝 Docker 就是"房地产开发":从施工图纸到小区物业的容器化指南
前端·javascript
计算机魔术师2 小时前
Rohan Paul 谈用时间跨度衡量智能体能力,并引用 OpenAI 自动化研究实习生里程碑
前端
kyriewen2 小时前
我手写了个极简版 React Router——才搞懂 v6 为什么砍了这么多 API
前端·javascript·程序员
a1117762 小时前
Shirone 二次元博客主题 开源
前端·开源
IT_陈寒3 小时前
Python的切片赋值把我坑惨了,这不是bug是特性
前端·人工智能·后端
掘金酱3 小时前
【社区公告】致每一位掘友:关于这次调整, 想再说几句
前端·人工智能