效果

在这里插入图片描述

源码

这是页面,记得配置pages.json

<template>
	<view class="bg-white camera-continer" :style="[state.style]">
		<camera :device-position="state.devicePosition" :flash="state.flashLamp" style="width: 100%;height: 100%;">
			<cover-view class="camera-content">
				<cover-view class="text-white padding water-mark">
					<cover-image style="width: 118px; height: 40px;" src="@/static/lsf-logo.png"></cover-image>
					<cover-view class="margin-top-sm address">{{ state.address }}</cover-view>
					<cover-view class="margin-top-sm">{{ state.time }}</cover-view>
				</cover-view>
				<cover-view class="padding bg-white flex justify-around align-center handler">
					<cover-image class="camera-icon" src="@/static/img/icons/camera-switching-fill.png"
						@click="switchCamera"></cover-image>
					<cover-image class="camera-btn" src="@/static/img/icons/photography-creativi.png"
						@click="takePhoto"></cover-image>
					<cover-image class="lamp-icon" :src="'/static/img/icons/'+lampIcon+'.png'" @click="switchLamp"></cover-image>
				</cover-view>
			</cover-view>
		</camera>
	</view>
	<canvas :style="{ width: state.width + 'px', height: state.height + 'px' }" canvas-id="canvas-photo"></canvas>
</template>

<script setup lang="ts">
	/**
		 * @author        							全易
		 * @time          							2024-08-15 10:10:00
		 * @description   							水印相机
		 * @use													父页面这样跳: https://blog.csdn.net/qq_42618566/article/details/141322992
																	uni.navigateTo({
																		url: "/components/watermark-camera",
																		events: {
																			emits(data) {
																				console.log("水印相机的照片:", data)
																			}
																		}
																	})
	 */
	import CheckinApi from "@/apps/check-in/api/checkin"
	import { watch, ref, reactive, computed, onBeforeUnmount, onMounted, getCurrentInstance } from "vue";
	import { onShow, onLoad } from "@dcloudio/uni-app";
	import { Tool } from "../utils/Tool";





	let interval = null;
	const instance = getCurrentInstance();
	const state = reactive({
		width: null,
		height: null,
		devicePosition: "back",
		flashLamp: "auto",
		time: "时间:",
		address: "地址:加载中...",
		imgList: [],
		style: { 'padding-bottom': null }
	});

	uni.startDeviceMotionListening({
		success: (e) => {
			console.log("监听设备方向:", e);
			uni.onDeviceMotionChange((res) => {
				// console.log("设备方向:", res)
				const alpha = res.alpha;
				const beta = res.beta;
				const gamma = res.gamma;
				if (Math.abs(beta) < 10 && Math.abs(gamma) < 10) {
					console.log("正竖屏");
				} else if (Math.abs(beta) > 170 && Math.abs(gamma) < 10) {
					console.log("倒竖屏");
				} else if (Math.abs(beta) < 10 && Math.abs(gamma - 90) < 10) {
					console.log("右横屏");
				} else if (Math.abs(beta) < 10 && Math.abs(gamma + 90) < 10) {
					console.log("左横屏");
				}
			});
		}
	});

	initWaterMark();
	const systemInfo = uni.getSystemInfoSync();
	state.style = {
		'padding-bottom': (systemInfo.safeAreaInsets.bottom || 15) + 'px'
	}

	onHide(() => {
		uni.stopDeviceMotionListening();
		clearInterval(interval);
	})


	const lampIcon = computed(() => {
		switch (state.flashLamp) {
			case "off":
				return "flash_off";
			case "on":
				return "flash";
			case "auto":
				return "flash_auto";
			default:
				return "flash_auto"
		}
	})

	function initWaterMark() {
		getPosition();
		interval = setInterval(updateTime(), 60000);
	};

	function updateTime() {
		const now = new Date().getTime()
		state.time = "时间:" + uni.$u.timeFormat(now, 'yyyy-mm-dd hh:MM');
		// state.time += Tool.now()
		return updateTime;
	};

	// 切换摄像头
	function switchCamera() {
		if (state.devicePosition === 'front') {
			state.devicePosition = 'back'
		} else {
			state.devicePosition = 'front'
		}
	};

	// 切换闪光灯
	function switchLamp() {
		if (state.flashLamp === 'auto') {
			state.flashLamp = 'on'
		} else if (state.flashLamp === 'on') {
			state.flashLamp = 'off'
		} else if (state.flashLamp === 'off') {
			state.flashLamp = 'auto'
		}
	};

	function getPosition() {
		uni.getLocation({
			type: 'gcj02',
			async success(res) {
				const position = await CheckinApi.reverseGeocoding({
					lon: res.longitude,
					lat: res.latitude
				});
				if (position.code === 0) {
					state.address = "地址:" + position.data.address;
				}
			},
			fail(err) {
				console.log('调用Location接口异常', err);
				if (err?.errMsg?.includes("fail auth deny")) {
					uni.getSetting({
						success: (res) => {
							if (!res.authSetting['scope.userLocation']) {
								uni.showModal({
									title: '未开启地理授权',
									content: '您的位置信息将用于连锁峰水印相机的展示',
									confirmText: "授权",
									success: res => {
										if (res.confirm) {
											uni.openSetting()
										}
									}
								})
							}
						}
					})
				} else {
					uni.$u.toast('获取地理位置失败,请确认手机的定位服务是否打开!');
				}
			}
		})
	};

	function takePhoto() {
		if (state.address === '地址:加载中...') {
			return uni.showToast({
				icon: "none",
				title: "地址未加载出来"
			})
		};

		uni.showLoading({
			title: "正在生成",
			mask: true
		});
		const ccc = uni.createCameraContext();
		ccc.takePhoto({
			quality: 'high',
			success: (res) => {
				createWatermark(res.tempImagePath);
			}
		});
	};


	function createWatermark(image) {
		uni.getImageInfo({
			src: image,
			success: res => {
				state.width = res.width / 3;
				state.height = res.height / 3.01;
				const ctx = uni.createCanvasContext('canvas-photo');
				
				const padding = 10; // 边距
				const fontSize = 11; // 字号
				const fontColor = '#FFFFFF'; // 字号
				const lineHeight = 15; // 行高
				
				ctx.drawImage(image, 0, 0, state.width, state.height);
				ctx.drawImage("/static/lsf-logo.png", padding, state.height * 0.83, 70, 20);

				// 文本集合
				const text = ["时间:" + Tool.now(), state.address]; // 需要填充两个文本
				let nextStart = state.height * 0.91; // 文本的起点
				text.forEach((line) => {
					nextStart = fillFeedText({
						ctx,
						text: line,
						x: padding,
						y: nextStart,
						maxWidth: state.width - padding - 10,
						lineHeight,
						color: fontColor,
						size: fontSize
					})
				});

				ctx.draw(false, () => {
					setTimeout(() => {
						uni.canvasToTempFilePath({
							canvasId: 'canvas-photo',
							success: ctf => {
								uni.navigateBack();
								const eventChannel = instance.proxy.getOpenerEventChannel();
								eventChannel.emit('emits', {
									image: ctf.tempFilePath
								});
								uni.hideLoading();
							}
						});
					}, 100)
				});
			}
		});
	};


	// 填充自动换行的文本
	function fillFeedText({ ctx, text, x, y, maxWidth, lineHeight, color, size }) {
		// 文本配置
		ctx.setFontSize(size);
		ctx.setFillStyle(color);
		ctx.setShadow(0, 0, 3, 'rgba(0, 0, 0, 0.4)');
		ctx.fillStyle = '#FFFFFF'; // 填充颜色(黑色)
		ctx.strokeStyle = '#FFFFFF'; // 边框颜色
		ctx.lineWidth = 0.8; // 边框宽度
		
		// 计算文本换行宽高,换行逻辑
		const words = text.split('');
		let line = '';
		const lines = [];
		for (let i = 0; i < words.length; i++) {
			const word = words[i];
			const testLine = line + word;
			const metrics = ctx.measureText(testLine);
			if (metrics.width > maxWidth && i > 0) {
				lines.push(line);
				line = word;
			} else {
				line = testLine;
			}
		};
		// 填充文本
		lines.push(line);
		let nextStart : number;
		lines.forEach((line, index) => {
			ctx.fillText(line, x, y + ((index++) * lineHeight), maxWidth);
			nextStart = y + ((index++) * lineHeight)
		});

		return nextStart;
	}
</script>

<style scoped lang="scss">
	.camera-continer {
		width: 100vw;
		height: 100vh;
		overflow: hidden;
	}

	.camera-content {
		position: absolute;
		bottom: 0;
		left: 0;
		width: 100%;

		.address {
			white-space: normal;
			/* 允许换行 */
			overflow-wrap: break-word;
			/* 在单词边界处换行 */
			word-wrap: break-word;
			/* 兼容旧版本的浏览器 */
			max-width: 100%;
		}

		.water-mark {}

		.handler {
			padding-bottom: 0;

			.camera-icon,
			.lamp-icon {
				width: 60rpx;
			}

			.camera-btn {
				width: 80rpx;
				padding: 15px;
				border: 8rpx #434343 solid;
				border-radius: 50%;
			}
		}
	}

	#canvas-photo {
		position: fixed;
		top: -999999px;
	}
</style>

uni-app canvas文本自动换行 https://quanyi.blog.csdn.net/article/details/142566671

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐