Skip to content

ICanvas - 语音录制波 ​

演示 ​

预览

介绍 ​

这是语音录制绘制线条的动画,主要是演示如何批量的绘制元素并添加动画。

示例源码 ​

uvue
vue
<template>

	<!-- #ifdef MP-WEIXIN -->
	<page-meta :page-style="`background-color:${xThemeConfigBgColor}`">
		<navigation-bar :background-color="xThemeConfigNavBgColor"
			:front-color="xThemeConfigNavFontColor"></navigation-bar>
	</page-meta>
	<!-- #endif -->

	<x-sheet>
		<x-text font-size="18" class=" text-weight-b mb-8">我正在听</x-text>
		<x-text color="#999999" class="mb-8">声音录制波场</x-text>

	</x-sheet>
	<x-sheet color="#000" dark-color="#000" :padding="['0']">
		<view class="xICanvasHost" :id="canvasId" :style="{height: canvasH + 'px'}">
			<x-skia-u ref="skiaRef" :width="canvasW" :height="canvasH" @ready="onSkiaReady"></x-skia-u>
			<view class="xICanvasHit"
				@click="bindEvents" @touchstart="bindEvents" @touchmove.stop="bindEvents"
				@touchend="bindEvents" @touchcancel="bindEvents" @mousedown="bindEvents"
				@mousemove="bindEvents" @mouseup="bindEvents"
				<!-- #ifdef WEB -->
				@mouseleave="bindEvents"
				<!-- #endif -->
			></view>
		</view>
	</x-sheet>


</template>

<script setup>
	import { ICanvas, ICanvasOptional, ILine, IShapeOptional,
	IRect, IText, IShapeVector2d
	} from '@/uni_modules/tmx-ui';

	const canvasId = "xCanvas-" + Math.random().toString(16).substring(4)
	const canvasW = Math.max(1, Math.floor(uni.getWindowInfo().windowWidth))
	const canvasH = 450
	const skiaRef = ref(null)
	const proxy = getCurrentInstance()?.proxy ?? null;
	const xcanvas = shallowRef<ICanvas>(new ICanvas({ canvasId: canvasId, component: proxy as any | null } as ICanvasOptional));

	let voiceLines : ILine[] = []
	let waveTickId : number = -1
	const waveColors = ['#00ff88', '#00ccff', '#ff6b6b', '#ffaa00', '#aa00ff'] as string[]

	const bindEvents = (evt : UniTouchEvent | UniPointerEvent | UniMouseEvent) => {
		xcanvas.value.bindEvent(evt)
	}

	// 创建语音波动线条
	const createVoiceLines = () => {
		if (xcanvas.value == null) return;
		const render = xcanvas.value! as ICanvas

		const centerX = render.boxWidth / 2
		const centerY = render.boxHeight / 2
		const lineCount = 30 // 线条数量
		const lineSpacing = 10
		const baseHeight = 20 // 基础高度
		const maxHeight = 120 // 最大高度

		// 计算起始X位置,使线条居中
		const startX = centerX - (lineCount - 1) * lineSpacing / 2

		// 创建多条垂直线条
		for (let i = 0; i < lineCount; i++) {
			const x = startX + i * lineSpacing

			// 为每条线设置不同的初始高度
			const initialHeight = baseHeight + Math.random() * (maxHeight - baseHeight)

		const line = new ILine({
			pointStart: { x: x, y: centerY - initialHeight / 2 } as IShapeVector2d,
			pointEnd: { x: x, y: centerY + initialHeight / 2 } as IShapeVector2d,
			stroke: "#00ff88",
			strokeWidth: 2,
			lineCap: 'round'
		} as IShapeOptional, render)

			voiceLines.push(line)
			render.addShape(line)
		}
	}

	const startWave = () => {
		if (xcanvas.value == null) return;
		const render = xcanvas.value! as ICanvas
		const baseHeight = 20
		const maxHeight = 120
		const phases : number[] = []
		const speeds : number[] = []
		const amps : number[] = []
		const n = voiceLines.length
		for (let i = 0; i < n; i++) {
			phases.push(i * 0.5)
			speeds.push(0.004 + Math.random() * 0.003)
			amps.push(baseHeight + Math.random() * (maxHeight - baseHeight))
		}
		if (waveTickId >= 0) {
			render.unregisterTick(waveTickId)
			waveTickId = -1
		}
		waveTickId = render.registerTick((time : number) : boolean => {
			const colorLen = waveColors.length
			for (let i = 0; i < n; i++) {
				const pec = Math.sin(time * speeds[i] + phases[i]) * 0.5 + 0.5
				let colorIndex = Math.floor(pec * (colorLen - 1))
				if (colorIndex < 0) colorIndex = 0
				if (colorIndex >= colorLen) colorIndex = colorLen - 1
				voiceLines[i].setStroke(waveColors[colorIndex])
				voiceLines[i].setHeight(pec * amps[i])
			}
			return true
		})
	}

	// 初始化绘制
	const drawer = () => {
		if (xcanvas.value == null) return;
		const render = xcanvas.value! as ICanvas

		// 设置背景
		const bgRect = new IRect({
			x: 0,
			y: 0,
			width: render.boxWidth,
			height: render.boxHeight,
			fill: "#000",
			fillGradient: ['45deg', '#1a1a2e 0%', '#16213e 50%', '#0f3460 100%']
		} as IShapeOptional, render)
		render.addShape(bgRect)

		// 添加标题文字
		const titleText = new IText({
			text: "正在录制...",
			fontSize: 24,
			textAlign: 'center',
			x: 0,
			y: 80,
			width: render.boxWidth,
			fill: "#00ff88",
		}, render)
		render.addShape(titleText)

		// 添加提示文字
		const hintText = new IText({
			text: "语音波形实时显示",
			fontSize: 16,
			textAlign: 'center',
			x: 0,
			y: render.boxHeight - 80,
			width: render.boxWidth,
			fill: "#ffffff",
			opacity: 0.7
		}, render)
		render.addShape(hintText)

		createVoiceLines()
		startWave()
		render.render()
	}

	const onSkiaReady = () => {
		const ctx = skiaRef.value?.getContext("2d")
		if (ctx == null) return
		xcanvas.value.onSurfaceReady(ctx, drawer)
	}

	onBeforeUnmount(() => {
		if (waveTickId >= 0 && xcanvas.value != null) {
			xcanvas.value.unregisterTick(waveTickId)
			waveTickId = -1
		}
		voiceLines = [] as ILine[]
		xcanvas.value?.destroy()
	})
</script>

<style>
	.xICanvasHost { position: relative; width: 100%; overflow: hidden; }
	.xICanvasHit { position: absolute; left: 0; top: 0; right: 0; bottom: 0; }
</style>
最近更新