uniapp中或小程序中使用背景水印(canvas实现+原生组件层级高的解决)

文前推荐一下👉前端必备工具推荐网站(图床、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中点击文件打开新页面查看文件


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wantLG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值