功能需求说明
需求说明:客户那边要求,给目前正在使用的安卓app添加水印,水印上标注上登录人,登录人电话号码和登录日期。
因为公司原因,就不放效果图了,大概就是水印效果图,下面大概讲述一下实现方案。
先后查询了react-native-image-marker(一直安装不上,我的react-native版本为0.68.2)这个仓库和react-native-watermarker(库最后一次更新7年前,然后放弃)
思考实现
根据同事的提醒,表示可以参考一下web端实现添加水印的办法。当时考虑到两个小问题,一个是如何给全局加水印,另外一个则是水印肯定是要加在最上面的,那么点击事件如何穿透呢。
第一个问题很好解决,如何给全局加水印,react-native中也有关于路由的地方,就是使用react-navigation这个库来实现,我们可以在这个地方给所有路由的组件包装一下,然后添加一下自己编写的waterMark组件。
第二个问题我是在网上搜才发现有一个属性是"ponitEvents"属性,这个属性大概就是用来做点击事件穿透的。
好了,两个问题都解决了,开始下一步,进行编写代码
代码实现
我们需要改动的地方有两个地方,一个是找到需要统一修改路由的地方,第二个就是编写一个水印的组件
修改路由的地方大概就是你遍历Stack.Screen的地方,代码大概可以这么写(虽然会出现一些问题),可能会有一个props传递异常的一个黄色警告,但对于功能实现并无影响。
js
const WaterMarkCom = memo(({Component, props}) => {
return (
<View
style={{
height: '100%',
width: '100%',
position: 'relative',
}}>
<View
style={{
position: 'absolute',
top: 0,
right: 0,
zIndex: -99,
bottom: 0,
left: 0,
}}>
<Component {...props} />
</View>
<WaterMark />
</View>
);
}, []);
const WaterComponent = props => {
return <WaterMarkCom props={props} Component={item.component} />;
};
至于我为什么套了一层,是因为莫名出现了有的视图被遮掩的情况。
waterMark组件的实现
其实我觉得最难的就是如何把数据内嵌进去,由于技术运气等其他原因,我没有使用webView中essage的方法将数据传递给html。去渲染页面,而是使用的injectedJavascript和使用reload更新这种办法来实现的动态添加水印。
下面为代码,免责声明,关于html生成水印的办法,我是照搬掘金内一个用户的代码
js
import React, {memo, useRef} from 'react';
import moment from 'moment';
import {useEffect} from 'react';
import {useSelector} from 'react-redux';
import {WebView} from 'react-native-webview';
import {View, Platform, Dimensions} from 'react-native';
const WaterMark = () => {
const markRef = useRef();
const user = useSelector(state => state?.user?.value);
const renderTextJs = function (user) {
// 我使用的是这里添加来防止页面不渲染
let arr = [];
if (user.name) {
arr = [`${user.name}`, `${user.tel}`, moment().format('YYYY/MM/DD')];
} else {
arr = ['', '', '']; // 退出登录后,置为空字符串
}
return `
// 参数
// str---水印文本 用来展示的文字
// id---唯一ID 当前水印区域的唯一标识,用于区分多个区域水印
// dom---DOM元素类名 传入类名则为区域水印,否则全屏水印
const setWatermark = (strArr, id, dom) => {
// 如果页面上已经存在该ID的水印元素,则先移除
if (document.getElementById(id) !== null) document.body.removeChild(document.getElementById(id));
// 创建一个canvas元素,用于绘制水印
const can = document.createElement('canvas');
// 设置canvas的宽度和高度
can.width = 180;
can.height = 120;
// 获取canvas的2d绘图上下文
const cans = can.getContext('2d');
// 旋转绘图上下文,设置水印的倾斜角度
cans.rotate((-20 * Math.PI) / 180);
// 设置水印文本的字体样式和大小
cans.font = '14px KaiTi';
// 设置水印文本的颜色和透明度
cans.fillStyle = 'rgba(200, 200, 200, 0.50)';
// 设置水印文本的基线对齐方式
cans.textBaseline = 'middle';
// 在canvas上绘制水印文本
cans.fillText(strArr[0], 20, 60);
cans.fillText(strArr[1], 20, 80);
cans.fillText(strArr[2], 20, 100);
// 创建一个变量用于接收DOM
let div;
// 如果传入了dom参数,则获取该类名的DOM元素,将水印添加到该元素上
if (dom) {
div = document.querySelector('.' + dom);
}
// 设置div元素的ID,用于后续识别和移除水印
div.id = id;
// 设置div元素不参与鼠标事件,避免影响页面其他元素的交互
div.style.pointerEvents = 'none';
// 设置div元素的z-index层级非常高,确保其显示在页面其他元素之上
div.style.zIndex = '10000000';
// 设置div元素的背景为水印图片,并设置为重复显示,从而铺满整个页面
div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat';
// 返回水印元素的ID,可用于后续移除水印
return id;
};
setWatermark(${JSON.stringify(arr)}, 11, 'box');
`;
};
useEffect(() => {
if (user.name) {
markRef.current.reload();
}
}, [user]);
return (
<View
pointerEvents="none"
style={{
height: Dimensions.get('window').height,
width: Dimensions.get('window').width + 20,
position: 'absolute',
left: -20, //左侧空白太多
bottom: 0,
top: 0,
right: 0,
backgroundColor: 'transparent', // 透明
}}>
<WebView
ref={markRef}
injectedJavaScript={renderTextJs(user)}
style={{
height: Dimensions.get('window').height,
width: Dimensions.get('window').width + 20, //添加上左移位置,防止右侧不渲染
backgroundColor: 'transparent',
}}
scalesPageToFit={Platform.OS !== 'ios'}
originWhitelist={['*']}
useWebKit={true} // ios使用最新webkit内核渲染
allowUniversalAccessFromFileURLs={true}
geolocationEnabled={true}
mixedContentMode={'always'}
scrollEnabled={false}
javaScriptEnabled={true}
onMessage={event => {}}
source={{
html: `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Watermark</title>
</head>
<body>
<div class="box"></div>
</body>
<style>
.box {
height: 1000px;
width: 1000px;
overflow: hidden;
background-color:transparent !important;
}
</style>
</html>`,
}}
/>
</View>
);
};
export default memo(WaterMark);
上述就是代码实现了,目前我是能实现app全局加水印的,对于未登录等情况也进行了一个兼容性处理
我分享出来,是因为,我在网上搜,react-native加水印,里面的内容都比较老,或者不太易读,我分享出来也算是做一个思路扩展。
有更好的方式,希望有底下留言,这样以后大家遇到问题,也能更好的解决问题。