文前推荐一下👉前端必备工具推荐网站(图床、API和ChatAI、智能AI简历、AI思维导图神器等实用工具):
站点入口:http://luckycola.com.cn/
图床:https://luckycola.com.cn/public/dist/#/imghub
多种API:https://luckycola.com.cn/public/dist/#/
ChatAI:https://luckycola.com.cn/public/dist/#/chatAi
AI思维导图神器:https://luckycola.com.cn/public/dist/#/aiQStore/aiMindPage
老规矩前言:需求是这样的,在小程序上加一个水印效果,不过要在最下面加(层级最低的意思),大家都知道水印可以用很多个盒子拼成一个水印;也可以用canvas生成,但canvas是原生组件,层级太高,会出现一系列问题。这里我用了我能想到的做法,有更好的方法请大佬们指教。
代码结构
<!-- 水印 -->
<view class='water_top'>
<canvas canvas-id='watermarkCanvas' style='width:100%;height:100%'></canvas>
</view>
<script>
export default {
data() {
return {
}
},
onLoad() {
this.drowsyUserinfo('高禄')
},
methods: {
drowsyUserinfo(name) {
const name_xx = name;
const ctx = uni.createCanvasContext("watermarkCanvas");
//文字的旋转角度
ctx.rotate(45 * Math.PI / 180);
ctx.setFontSize(16); //wx设置一个文字大小
// 获取到名字的宽度
const textWidth = ctx.measureText(name_xx).width;
// 每个名字带一个空字作为间距
const allTextWidth = textWidth + textWidth/name_xx.length;
//对斜对角线以左部分进行文字的填充
for (let j = 1; j < 15; j++) { // for循环代表纵向循环文字
ctx.beginPath();
ctx.setFillStyle("rgba(49, 106, 236,1)");
ctx.fillText(name_xx, 0, 50 * j);
for (let i = 1; i < 20; i++) { //这个for循环代表横向循环,
ctx.beginPath();
ctx.setFillStyle("rgba(49, 106, 236,1)");
ctx.fillText(name_xx, allTextWidth * i, 50 * j);
}
}
//两个for循环的配合,使得文字充满斜对角线的左下部分
//对斜对角线以右部分进行文字的填充逻辑同上
for (let j = 0; j < 15; j++) {
ctx.beginPath();
ctx.setFillStyle("rgba(49, 106, 236,1)");
ctx.fillText(name_xx, 0, -50 * j);
for (let i = 1; i < 20; i++) {
ctx.beginPath();
ctx.setFillStyle("rgba(49, 106, 236,1)");
ctx.fillText(name_xx, allTextWidth * i, -50 * j);
}
}
ctx.draw();
},
}
}
</script>
<style lang="scss" scoped>
.water_top {
position: fixed;
z-index: 1;
opacity: 1;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: #3a72f5;
}
.home-container{
position: relative;
z-index: 5;
}
</style>
这样虽然绘制出来了,在开发者工具中没有问题,但是在真机调试出现问题
问题一:绘制后,真机调试出问题,canvas原生组件层级太高
由于canvas是原生组件,所以原生组件不管设置了多少z-index,还是在最高层级
由于我这个是要作为背景的存在,
解决思路:先用canvas画出,用draw的回调进行转图片,然后转成图片渲染到页面
/**
* 绘制水印
*/
drowsyUserinfo(name) {
const name_xx = name;
const ctx = uni.createCanvasContext("watermarkCanvas");
//文字的旋转角度
ctx.rotate((45 * Math.PI) / 180);
ctx.setFontSize(16); //wx设置一个文字大小
// 获取到名字的宽度
const textWidth = ctx.measureText(name_xx).width;
// 每个名字带一个空字作为间距
const allTextWidth = textWidth + textWidth / name_xx.length;
//对斜对角线以左部分进行文字的填充
for (let j = 1; j < 15; j++) {
// for循环代表纵向循环文字
ctx.beginPath();
ctx.setFillStyle("rgba(49, 106, 236,1)");
ctx.fillText(name_xx, 0, 50 * j);
for (let i = 1; i < 20; i++) {
//这个for循环代表横向循环,
ctx.beginPath();
ctx.setFillStyle("rgba(49, 106, 236,1)");
ctx.fillText(name_xx, allTextWidth * i, 50 * j);
}
}
//两个for循环的配合,使得文字充满斜对角线的左下部分
//对斜对角线以右部分进行文字的填充逻辑同上
for (let j = 0; j < 15; j++) {
ctx.beginPath();
ctx.setFillStyle("rgba(49, 106, 236,1)");
ctx.fillText(name_xx, 0, -50 * j);
for (let i = 1; i < 20; i++) {
ctx.beginPath();
ctx.setFillStyle("rgba(49, 106, 236,1)");
ctx.fillText(name_xx, allTextWidth * i, -50 * j);
}
}
ctx.draw(true, () => {
uni.canvasToTempFilePath(
{
x: 0,
y: 0,
canvasId: "watermarkCanvas",
fileType: "png",
quality: 1,
success: (result) => {
this.canvasImg = result.tempFilePath;
},
fail: (err) => {
this.canvasImg = "error";
},
complete: () => {
this.canvasFinsh = true;
},
},
this
);
});
},
<!-- 水印 -->
<view class='water_top'>
<canvas v-if="!canvasImg" canvas-id='watermarkCanvas' style='width:100%;height:100%;z-index:1'></canvas>
<image v-else :src="canvasImg" style='width:100%;height:100%;z-index:1;position: absolute;'/>
</view>
注意事项:必须在draw回调画图,不然真机调试会出现 canvasToTempFilePath: fail canvas is empty
目前在真机上层级可以正常显示
问题二:虽然可以在真机可以显示,但是在加载的瞬间会先绘制canvas(导致会有几帧的层级最高问题),然后才会显示image
解决方案:把canvas先用定位移除屏幕,然后把要覆盖在canvas上面的元素加上v-if判断条件(渲染图片地址),最后用封装的请求loading动画延长,造成加载数据的假象
<!-- 水印 -->
<view class="water_top">
<canvas
v-if="!canvasImg"
canvas-id="watermarkCanvas"
style="
width: 100%;
height: 100vh;
z-index: 1;
position: absolute;
left: -5000rpx;
top: -5000rpx;
"
></canvas>
<image
v-else
:src="canvasImg"
style="width: 100%; height: 100vh; z-index: 1; position: absolute"
/>
</view>
data() {
return {
canvasImg: null,
canvasFinsh: null,
waterMarkText: "gaolu" // 最后需要动态获取
};
},
简单的封装请求
import { BASE_API } from './config.js'
import Vue from "vue";
//将Promise对象封装在request()函数中
function request({ url: url, method: method = 'GET', data: data, timeClose: timeClose = 0, openLoading: openLoading = true, ...args }) {
//将uni.request请求API包装成一个Promise对象
return new Promise((resolve, reject) => {
if (openLoading) {
Vue.$loading.start();
}
// 查看是否是完整路径
const handleUrl = url.indexOf('http') == '0' ? url : (BASE_API + url);
uni.request({
url: handleUrl,
method,
data,
args,
timeout: 50000,
success: function (res) {
if (res.data.code == 20000) {
resolve(res.data);
} else {
uni.showToast({
title: res.data.message || res.data.error || "抱歉,网络出小差了",
icon: "none",
mask: true,
duration: 1700,
});
reject(res);
}
},
fail(err) {
uni.showToast({
title: err.message,
icon: "none",
mask: true,
});
reject(err);
},
complete() {
if (openLoading) {
setTimeout(() => {
Vue.$loading.finish();
}, timeClose)
}
},
});
});
}
export default request;
完美分割线~~~~
更多推荐:wantLG的《普歌-uniapp/vue根据文件路径后缀显示相应的icon图标,在uniapp中点击文件打开新页面查看文件》
- 作者:wantLG
- 本文源自:wantLG的《普歌-uniapp中或小程序中使用背景水印(canvas实现+原生组件层级高的解决)》
- 本文版权归作者和CSDN共有,欢迎转载,且在文章页面明显位置给出原文链接,未经作者同意必须保留此段声明,否则保留追究法律责任的权利。