first commit
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
:: BASE_DOC ::
|
||||
|
||||
## API
|
||||
|
||||
### Watermark Props
|
||||
|
||||
name | type | default | description | required
|
||||
-- | -- | -- | -- | --
|
||||
custom-style | Object | - | CSS(Cascading Style Sheets) | N
|
||||
alpha | Number | 1 | \- | N
|
||||
content | String | - | \- | N
|
||||
height | Number | - | \- | N
|
||||
is-repeat | Boolean | true | \- | N
|
||||
layout | String | rectangular | options: rectangular/hexagonal | N
|
||||
line-space | Number | 16 | \- | N
|
||||
movable | Boolean | false | \- | N
|
||||
move-interval | Number | 3000 | \- | N
|
||||
offset | Array | - | Typescript:`Array<number>` | N
|
||||
removable | Boolean | true | \- | N
|
||||
rotate | Number | -22 | \- | N
|
||||
watermark-content | Object / Array | - | Typescript:`WatermarkText\|WatermarkImage\|Array<WatermarkText\|WatermarkImage>` | N
|
||||
width | Number | - | \- | N
|
||||
x | Number | - | \- | N
|
||||
y | Number | - | \- | N
|
||||
z-index | Number | - | \- | N
|
||||
|
||||
### Watermark Slots
|
||||
|
||||
name | Description
|
||||
-- | --
|
||||
\- | \-
|
||||
content | \-
|
||||
|
||||
### WatermarkText
|
||||
|
||||
name | type | default | description | required
|
||||
-- | -- | -- | -- | --
|
||||
font-color | String | rgba(0,0,0,0.1) | \- | N
|
||||
font-family | String | - | font-family configuration for watermark text | N
|
||||
font-size | Number | 16 | \- | N
|
||||
font-weight | String | normal | options: normal/lighter/bold/bolder | N
|
||||
text | String | - | \- | N
|
||||
|
||||
### WatermarkImage
|
||||
|
||||
name | type | default | description | required
|
||||
-- | -- | -- | -- | --
|
||||
is-grayscale | Boolean | false | \- | N
|
||||
url | String | - | \- | N
|
||||
|
||||
### CSS Variables
|
||||
|
||||
The component provides the following CSS variables, which can be used to customize styles.
|
||||
Name | Default Value | Description
|
||||
-- | -- | --
|
||||
--watermark-left-0 | --watermark-left-0 | -
|
||||
--watermark-left-25 | --watermark-left-25 | -
|
||||
--watermark-left-50 | --watermark-left-50 | -
|
||||
--watermark-left-75 | --watermark-left-75 | -
|
||||
--watermark-top-0 | --watermark-top-0 | -
|
||||
--watermark-top-25 | --watermark-top-25 | -
|
||||
--watermark-top-50 | --watermark-top-50 | -
|
||||
--watermark-top-75 | --watermark-top-75 | -
|
||||
115
uni_modules/tdesign-uniapp/components/watermark/README.md
Normal file
115
uni_modules/tdesign-uniapp/components/watermark/README.md
Normal file
@@ -0,0 +1,115 @@
|
||||
---
|
||||
title: Watermark 水印
|
||||
description: 给页面的某个区域加上水印。
|
||||
spline: data
|
||||
isComponent: true
|
||||
---
|
||||
|
||||
|
||||
## 引入
|
||||
|
||||
可在 `main.ts` 或在需要使用的页面或组件中引入。
|
||||
|
||||
```js
|
||||
import TWatermark from '@tdesign/uniapp/watermark/watermark.vue';
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 组件类型
|
||||
|
||||
#### 文字水印
|
||||
|
||||
{{ base }}
|
||||
|
||||
#### 图片水印
|
||||
|
||||
{{ image }}
|
||||
|
||||
#### 图片灰阶水印
|
||||
|
||||
{{ gray }}
|
||||
|
||||
#### 多行图文水印
|
||||
|
||||
{{ multi-line }}
|
||||
|
||||
#### 多行图文灰阶水印
|
||||
|
||||
{{ multi-line-gray }}
|
||||
|
||||
#### 运动文字水印
|
||||
|
||||
{{ move-text }}
|
||||
|
||||
#### 运动图片水印
|
||||
|
||||
{{ move-image }}
|
||||
|
||||
### 不同布局的水印
|
||||
通过设置 layout 使用不同的布局。
|
||||
|
||||
{{ layout }}
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### Watermark Props
|
||||
|
||||
名称 | 类型 | 默认值 | 描述 | 必传
|
||||
-- | -- | -- | -- | --
|
||||
custom-style | Object | - | 自定义样式 | N
|
||||
alpha | Number | 1 | 水印整体透明度,取值范围 [0-1] | N
|
||||
content | String | - | 水印所覆盖的内容节点 | N
|
||||
height | Number | - | 水印高度 | N
|
||||
is-repeat | Boolean | true | 水印是否重复出现 | N
|
||||
layout | String | rectangular | 水印的布局方式,rectangular:矩形,即横平竖直的水印;hexagonal:六边形,即错位的水印。可选项:rectangular/hexagonal | N
|
||||
line-space | Number | 16 | 行间距,只作用在多行(`content` 配置为数组)情况下 | N
|
||||
movable | Boolean | false | 水印是否可移动 | N
|
||||
move-interval | Number | 3000 | 水印发生运动位移的间隙,单位:毫秒 | N
|
||||
offset | Array | - | 水印在画布上绘制的水平和垂直偏移量,正常情况下水印绘制在中间位置,即 `offset = [gapX / 2, gapY / 2]`。TS 类型:`Array<number>` | N
|
||||
removable | Boolean | true | 水印是否可被删除 | N
|
||||
rotate | Number | -22 | 水印旋转的角度,单位 ° | N
|
||||
watermark-content | Object / Array | - | 水印内容,需要显示多行情况下可配置为数组。TS 类型:`WatermarkText\|WatermarkImage\|Array<WatermarkText\|WatermarkImage>` | N
|
||||
width | Number | - | 水印宽度 | N
|
||||
x | Number | - | 水印之间的水平间距 | N
|
||||
y | Number | - | 水印之间的垂直间距 | N
|
||||
z-index | Number | - | 水印元素的 `z-index`,默认值写在 CSS 中 | N
|
||||
|
||||
### Watermark Slots
|
||||
|
||||
名称 | 描述
|
||||
-- | --
|
||||
\- | 默认插槽,作用同 `content` 插槽
|
||||
content | 自定义 `content` 显示内容
|
||||
|
||||
### WatermarkText
|
||||
|
||||
名称 | 类型 | 默认值 | 描述 | 必传
|
||||
-- | -- | -- | -- | --
|
||||
font-color | String | rgba(0,0,0,0.1) | 水印文本文字颜色 | N
|
||||
font-family | String | - | 水印文本文字字体 | N
|
||||
font-size | Number | 16 | 水印文本文字大小 | N
|
||||
font-weight | String | normal | 水印文本文字粗细。可选项:normal/lighter/bold/bolder | N
|
||||
text | String | - | 水印文本内容 | N
|
||||
|
||||
### WatermarkImage
|
||||
|
||||
名称 | 类型 | 默认值 | 描述 | 必传
|
||||
-- | -- | -- | -- | --
|
||||
is-grayscale | Boolean | false | 水印图片是否需要灰阶显示 | N
|
||||
url | String | - | 水印图片源地址,为了显示清楚,建议导出 2 倍或 3 倍图 | N
|
||||
|
||||
### CSS Variables
|
||||
|
||||
组件提供了下列 CSS 变量,可用于自定义样式。
|
||||
名称 | 默认值 | 描述
|
||||
-- | -- | --
|
||||
--watermark-left-0 | --watermark-left-0 | -
|
||||
--watermark-left-25 | --watermark-left-25 | -
|
||||
--watermark-left-50 | --watermark-left-50 | -
|
||||
--watermark-left-75 | --watermark-left-75 | -
|
||||
--watermark-top-0 | --watermark-top-0 | -
|
||||
--watermark-top-25 | --watermark-top-25 | -
|
||||
--watermark-top-50 | --watermark-top-50 | -
|
||||
--watermark-top-75 | --watermark-top-75 | -
|
||||
82
uni_modules/tdesign-uniapp/components/watermark/props.ts
Normal file
82
uni_modules/tdesign-uniapp/components/watermark/props.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
|
||||
* */
|
||||
|
||||
import type { TdWatermarkProps } from './type';
|
||||
export default {
|
||||
/** 水印整体透明度,取值范围 [0-1] */
|
||||
alpha: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
/** 水印所覆盖的内容节点 */
|
||||
content: {
|
||||
type: String,
|
||||
},
|
||||
/** 水印高度 */
|
||||
height: {
|
||||
type: Number,
|
||||
},
|
||||
/** 水印是否重复出现 */
|
||||
isRepeat: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/** 水印的布局方式,rectangular:矩形,即横平竖直的水印;hexagonal:六边形,即错位的水印 */
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'rectangular' as TdWatermarkProps['layout'],
|
||||
validator(val: TdWatermarkProps['layout']): boolean {
|
||||
if (!val) return true;
|
||||
return ['rectangular', 'hexagonal'].includes(val);
|
||||
},
|
||||
},
|
||||
/** 行间距,只作用在多行(`content` 配置为数组)情况下 */
|
||||
lineSpace: {
|
||||
type: Number,
|
||||
default: 16,
|
||||
},
|
||||
/** 水印是否可移动 */
|
||||
movable: Boolean,
|
||||
/** 水印发生运动位移的间隙,单位:毫秒 */
|
||||
moveInterval: {
|
||||
type: Number,
|
||||
default: 3000,
|
||||
},
|
||||
/** 水印在画布上绘制的水平和垂直偏移量,正常情况下水印绘制在中间位置,即 `offset = [gapX / 2, gapY / 2]` */
|
||||
offset: {
|
||||
type: Array,
|
||||
},
|
||||
/** 水印是否可被删除 */
|
||||
removable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/** 水印旋转的角度,单位 ° */
|
||||
rotate: {
|
||||
type: Number,
|
||||
default: -22,
|
||||
},
|
||||
/** 水印内容,需要显示多行情况下可配置为数组 */
|
||||
watermarkContent: {
|
||||
type: [Object, Array],
|
||||
},
|
||||
/** 水印宽度 */
|
||||
width: {
|
||||
type: Number,
|
||||
},
|
||||
/** 水印之间的水平间距 */
|
||||
x: {
|
||||
type: Number,
|
||||
},
|
||||
/** 水印之间的垂直间距 */
|
||||
y: {
|
||||
type: Number,
|
||||
},
|
||||
/** 水印元素的 `z-index`,默认值写在 CSS 中 */
|
||||
zIndex: {
|
||||
type: Number,
|
||||
},
|
||||
};
|
||||
121
uni_modules/tdesign-uniapp/components/watermark/type.ts
Normal file
121
uni_modules/tdesign-uniapp/components/watermark/type.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
|
||||
* */
|
||||
|
||||
export interface TdWatermarkProps {
|
||||
/**
|
||||
* 水印整体透明度,取值范围 [0-1]
|
||||
* @default 1
|
||||
*/
|
||||
alpha?: number;
|
||||
/**
|
||||
* 水印所覆盖的内容节点
|
||||
*/
|
||||
content?: string;
|
||||
/**
|
||||
* 水印高度
|
||||
*/
|
||||
height?: number;
|
||||
/**
|
||||
* 水印是否重复出现
|
||||
* @default true
|
||||
*/
|
||||
isRepeat?: boolean;
|
||||
/**
|
||||
* 水印的布局方式,rectangular:矩形,即横平竖直的水印;hexagonal:六边形,即错位的水印
|
||||
* @default rectangular
|
||||
*/
|
||||
layout?: 'rectangular' | 'hexagonal';
|
||||
/**
|
||||
* 行间距,只作用在多行(`content` 配置为数组)情况下
|
||||
* @default 16
|
||||
*/
|
||||
lineSpace?: number;
|
||||
/**
|
||||
* 水印是否可移动
|
||||
* @default false
|
||||
*/
|
||||
movable?: boolean;
|
||||
/**
|
||||
* 水印发生运动位移的间隙,单位:毫秒
|
||||
* @default 3000
|
||||
*/
|
||||
moveInterval?: number;
|
||||
/**
|
||||
* 水印在画布上绘制的水平和垂直偏移量,正常情况下水印绘制在中间位置,即 `offset = [gapX / 2, gapY / 2]`
|
||||
*/
|
||||
offset?: Array<number>;
|
||||
/**
|
||||
* 水印是否可被删除
|
||||
* @default true
|
||||
*/
|
||||
removable?: boolean;
|
||||
/**
|
||||
* 水印旋转的角度,单位 °
|
||||
* @default -22
|
||||
*/
|
||||
rotate?: number;
|
||||
/**
|
||||
* 水印内容,需要显示多行情况下可配置为数组
|
||||
*/
|
||||
watermarkContent?: WatermarkText | WatermarkImage | Array<WatermarkText | WatermarkImage>;
|
||||
/**
|
||||
* 水印宽度
|
||||
*/
|
||||
width?: number;
|
||||
/**
|
||||
* 水印之间的水平间距
|
||||
*/
|
||||
x?: number;
|
||||
/**
|
||||
* 水印之间的垂直间距
|
||||
*/
|
||||
y?: number;
|
||||
/**
|
||||
* 水印元素的 `z-index`,默认值写在 CSS 中
|
||||
*/
|
||||
zIndex?: number;
|
||||
}
|
||||
|
||||
export interface WatermarkText {
|
||||
/**
|
||||
* 水印文本文字颜色
|
||||
* @default rgba(0,0,0,0.1)
|
||||
*/
|
||||
fontColor?: string;
|
||||
/**
|
||||
* 水印文本文字字体
|
||||
* @default ''
|
||||
*/
|
||||
fontFamily?: string;
|
||||
/**
|
||||
* 水印文本文字大小
|
||||
* @default 16
|
||||
*/
|
||||
fontSize?: number;
|
||||
/**
|
||||
* 水印文本文字粗细
|
||||
* @default normal
|
||||
*/
|
||||
fontWeight?: 'normal' | 'lighter' | 'bold' | 'bolder';
|
||||
/**
|
||||
* 水印文本内容
|
||||
* @default ''
|
||||
*/
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export interface WatermarkImage {
|
||||
/**
|
||||
* 水印图片是否需要灰阶显示
|
||||
* @default false
|
||||
*/
|
||||
isGrayscale?: boolean;
|
||||
/**
|
||||
* 水印图片源地址,为了显示清楚,建议导出 2 倍或 3 倍图
|
||||
* @default ''
|
||||
*/
|
||||
url?: string;
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
import { loadImage } from '../../common/canvas/index';
|
||||
import { getWindowInfo } from '../../common/utils';
|
||||
|
||||
const ratio = getWindowInfo().pixelRatio || 1;
|
||||
|
||||
// 元素中心为旋转点执行旋转
|
||||
const drawRotate = (
|
||||
ctx,
|
||||
x,
|
||||
y,
|
||||
rotate,
|
||||
) => {
|
||||
ctx.translate(x, y);
|
||||
ctx.rotate((Math.PI / 180) * Number(rotate));
|
||||
ctx.translate(-x, -y);
|
||||
};
|
||||
|
||||
// 绘制文字
|
||||
const drawText = (
|
||||
ctx,
|
||||
x,
|
||||
y,
|
||||
markHeight,
|
||||
text,
|
||||
fontWeight,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
fillStyle,
|
||||
) => {
|
||||
ctx.font = `normal normal ${fontWeight} ${
|
||||
fontSize * ratio
|
||||
}px/${markHeight}px ${fontFamily}`;
|
||||
|
||||
ctx.fillStyle = fillStyle;
|
||||
ctx.textAlign = 'start';
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
ctx.fillText(text, x, y);
|
||||
};
|
||||
|
||||
export default async function generateBase64Url(
|
||||
canvas,
|
||||
canvasId,
|
||||
{
|
||||
width,
|
||||
height,
|
||||
gapX,
|
||||
gapY,
|
||||
offsetLeft,
|
||||
offsetTop,
|
||||
rotate,
|
||||
alpha,
|
||||
watermarkContent,
|
||||
lineSpace,
|
||||
watermarkColor,
|
||||
layout,
|
||||
},
|
||||
onFinish,
|
||||
onFinally,
|
||||
) {
|
||||
const isHexagonal = layout === 'hexagonal';
|
||||
|
||||
let ctx;
|
||||
|
||||
// #ifdef MP || H5
|
||||
ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
// #endif
|
||||
|
||||
if (!ctx) {
|
||||
ctx = uni.createCanvasContext(canvasId, this);
|
||||
}
|
||||
if (!ctx) {
|
||||
console.warn('当前环境不支持Canvas, 无法绘制水印');
|
||||
onFinish('');
|
||||
return;
|
||||
}
|
||||
|
||||
let actualBackgroundSize = {
|
||||
width: gapX + width,
|
||||
};
|
||||
|
||||
const canvasWidth = (gapX + width) * ratio;
|
||||
const canvasHeight = (gapY + height) * ratio;
|
||||
|
||||
const markWidth = width * ratio;
|
||||
const markHeight = height * ratio;
|
||||
|
||||
const dislocationRotateX = canvasWidth;
|
||||
const dislocationRotateY = canvasHeight;
|
||||
const dislocationDrawX = (gapX + width) * ratio;
|
||||
const dislocationDrawY = (gapY + height) * ratio;
|
||||
|
||||
canvas.width = canvasWidth;
|
||||
canvas.height = canvasHeight;
|
||||
|
||||
if (isHexagonal) {
|
||||
canvas.width = canvasWidth * 2;
|
||||
canvas.height = canvasHeight * 2;
|
||||
|
||||
// 两倍宽度+间距
|
||||
actualBackgroundSize = {
|
||||
width: gapX + width * 2 + width / 2,
|
||||
};
|
||||
}
|
||||
|
||||
ctx.globalAlpha = alpha;
|
||||
|
||||
// h5需要全局缩放
|
||||
// #ifdef H5 || APP-PLUS
|
||||
ctx.scale(1 / ratio, 1 / ratio);
|
||||
// #endif
|
||||
|
||||
ctx.fillStyle = 'transparent';
|
||||
ctx.fillRect(0, 0, markWidth, markHeight);
|
||||
|
||||
ctx.translate(offsetLeft * ratio, offsetTop * ratio);
|
||||
|
||||
const contents = Array.isArray(watermarkContent)
|
||||
? watermarkContent
|
||||
: [{ ...watermarkContent }];
|
||||
|
||||
let top = 0;
|
||||
|
||||
// 预处理
|
||||
contents.forEach((item) => {
|
||||
item.top = top;
|
||||
if (item.url) {
|
||||
top += height;
|
||||
} else if (item.text) {
|
||||
top += lineSpace;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 绘制水印内容
|
||||
const renderWatermarkItem = async (
|
||||
item,
|
||||
offsetX = 0,
|
||||
offsetY = 0,
|
||||
rotateX = 0,
|
||||
rotateY = 0,
|
||||
) => {
|
||||
if (item.url) {
|
||||
const { url, isGrayscale = false } = item;
|
||||
const img = await loadImage({
|
||||
canvas,
|
||||
src: url,
|
||||
});
|
||||
|
||||
ctx.save?.();
|
||||
drawRotate(ctx, rotateX, rotateY, rotate);
|
||||
|
||||
// TODO:其他技术栈修复了「灰度效果只影响图片,不影响文字」的bug,因为小程序不能创建临时canvas,暂时没有想到比较优雅的解决方案
|
||||
if (isGrayscale) {
|
||||
// #ifdef APP-PLUS
|
||||
ctx.drawImage(
|
||||
img,
|
||||
offsetX,
|
||||
offsetY + item.top * ratio,
|
||||
width * ratio / 2,
|
||||
height * ratio / 2,
|
||||
);
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-PLUS
|
||||
ctx.drawImage(
|
||||
img,
|
||||
offsetX,
|
||||
offsetY + item.top * ratio,
|
||||
width * ratio,
|
||||
height * ratio,
|
||||
);
|
||||
// #endif
|
||||
const imgData = await getImageData(ctx, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
const pixels = imgData.data;
|
||||
for (let i = 0; i < pixels.length; i += 4) {
|
||||
const lightness = (pixels[i] + pixels[i + 1] + pixels[i + 2]) / 3;
|
||||
pixels[i] = lightness;
|
||||
pixels[i + 1] = lightness;
|
||||
pixels[i + 2] = lightness;
|
||||
}
|
||||
await putImageData(ctx, imgData, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
} else {
|
||||
// #ifdef APP-PLUS
|
||||
ctx.drawImage(
|
||||
img,
|
||||
offsetX,
|
||||
(offsetY + item.top * ratio),
|
||||
width * ratio / 2,
|
||||
height * ratio / 2,
|
||||
);
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-PLUS
|
||||
ctx.drawImage(
|
||||
img,
|
||||
offsetX,
|
||||
offsetY + item.top * ratio,
|
||||
width * ratio,
|
||||
height * ratio,
|
||||
);
|
||||
// #endif
|
||||
}
|
||||
|
||||
ctx.restore?.();
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.text) {
|
||||
const {
|
||||
text,
|
||||
fontSize = 16,
|
||||
fontFamily = 'normal',
|
||||
fontWeight = 'normal',
|
||||
} = item;
|
||||
const fillStyle = item?.fontColor || watermarkColor;
|
||||
|
||||
ctx.save?.();
|
||||
drawRotate(ctx, rotateX, rotateY, rotate);
|
||||
drawText(
|
||||
ctx,
|
||||
offsetX,
|
||||
offsetY + item.top * ratio,
|
||||
markHeight,
|
||||
text,
|
||||
fontWeight,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
fillStyle,
|
||||
);
|
||||
ctx.restore?.();
|
||||
}
|
||||
};
|
||||
|
||||
// 矩形水印
|
||||
for (const item of contents) {
|
||||
await renderWatermarkItem(item, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
// 六边形水印
|
||||
if (isHexagonal) {
|
||||
for (const item of contents) {
|
||||
await renderWatermarkItem(
|
||||
item,
|
||||
dislocationDrawX,
|
||||
dislocationDrawY,
|
||||
dislocationRotateX,
|
||||
dislocationRotateY,
|
||||
);
|
||||
}
|
||||
}
|
||||
// #ifdef APP-PLUS
|
||||
ctx.draw();
|
||||
// #endif
|
||||
|
||||
|
||||
// 没有图片
|
||||
const canvasUrl = await exportCanvasImage.call(this, canvas, canvasId);
|
||||
onFinish(canvasUrl, actualBackgroundSize, ratio);
|
||||
|
||||
onFinally?.();
|
||||
}
|
||||
// 跨平台 Canvas 导出方法
|
||||
export function exportCanvasImage(canvas, canvasId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let parsed = false;
|
||||
// #ifdef H5 || MP
|
||||
resolve(canvas.toDataURL('image/png'));
|
||||
parsed = true;
|
||||
// #endif
|
||||
|
||||
if (parsed) return;
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
|
||||
query
|
||||
.select(`#${canvasId}`)
|
||||
.fields({ node: true, size: true })
|
||||
.exec(async (res) => {
|
||||
if (!res[0]?.node) {
|
||||
console.error('Canvas node not found');
|
||||
return;
|
||||
}
|
||||
|
||||
uni.canvasToTempFilePath({
|
||||
// #ifdef MP
|
||||
canvas: res[0].node,
|
||||
// #endif
|
||||
// #ifndef MP
|
||||
canvasId,
|
||||
// #endif
|
||||
success: res => resolve(res.tempFilePath),
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function getImageData(ctx, options) {
|
||||
let result;
|
||||
// #ifdef H5 || MP
|
||||
result = ctx.getImageData(options.x, options.y, options.width, options.height);
|
||||
// #endif
|
||||
|
||||
if (!result) {
|
||||
result = new Promise((resolve) => {
|
||||
uni.canvasGetImageData({
|
||||
canvasId: options.canvasId,
|
||||
x: options.x,
|
||||
y: options.y,
|
||||
width: options.width,
|
||||
height: options.height,
|
||||
success: (res) => {
|
||||
// 小程序/App 返回的数据结构需要转换
|
||||
resolve({
|
||||
data: new Uint8ClampedArray(res.data),
|
||||
width: res.width,
|
||||
height: res.height,
|
||||
});
|
||||
},
|
||||
}, this);
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
async function putImageData(ctx, imageData, options) {
|
||||
let result;
|
||||
// #ifdef H5 || MP
|
||||
ctx.putImageData(imageData, options.x, options.y);
|
||||
result = Promise.resolve();
|
||||
// #endif
|
||||
|
||||
if (!result) {
|
||||
result = new Promise((resolve, reject) => {
|
||||
uni.canvasPutImageData({
|
||||
canvasId: options.canvasId,
|
||||
x: options.x,
|
||||
y: options.y,
|
||||
width: imageData.width,
|
||||
height: imageData.height,
|
||||
data: imageData.data,
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
}, this);
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
export default function randomMovingStyle() {
|
||||
const align = Math.floor(Math.random() * 4);
|
||||
const p1 = Math.floor(Math.random() * 70) + 30;
|
||||
const leftTopLimit = 0;
|
||||
const bottomLimit = 95;
|
||||
const rightLimit = 90;
|
||||
const left0 = align === 1 ? rightLimit : align === 3 ? leftTopLimit : p1;
|
||||
const left25 = align === 0 ? rightLimit : align === 2 ? leftTopLimit : 100 - p1;
|
||||
const left50 = align === 1 ? leftTopLimit : align === 3 ? rightLimit : 100 - p1;
|
||||
const left75 = align === 0 ? leftTopLimit : align === 2 ? rightLimit : p1;
|
||||
const top0 = align === 0 ? leftTopLimit : align === 2 ? bottomLimit : p1;
|
||||
const top25 = align === 1 ? bottomLimit : align === 3 ? leftTopLimit : p1;
|
||||
const top50 = align === 0 ? bottomLimit : align === 2 ? leftTopLimit : 100 - p1;
|
||||
const top75 = align === 1 ? leftTopLimit : align === 3 ? bottomLimit : 100 - p1;
|
||||
|
||||
return {
|
||||
left0: `${left0}%`,
|
||||
left25: `${left25}%`,
|
||||
left50: `${left50}%`,
|
||||
left75: `${left75}%`,
|
||||
top0: `${top0}%`,
|
||||
top25: `${top25}%`,
|
||||
top50: `${top50}%`,
|
||||
top75: `${top75}%`,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
.t-watermark {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
@keyframes watermark {
|
||||
0% {
|
||||
left: var(--watermark-left-0);
|
||||
top: var(--watermark-top-0);
|
||||
}
|
||||
25% {
|
||||
left: var(--watermark-left-25);
|
||||
top: var(--watermark-top-25);
|
||||
}
|
||||
50% {
|
||||
left: var(--watermark-left-50);
|
||||
top: var(--watermark-top-50);
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
75% {
|
||||
left: var(--watermark-left-75);
|
||||
top: var(--watermark-top-75);
|
||||
}
|
||||
100% {
|
||||
left: var(--watermark-left-0);
|
||||
top: var(--watermark-top-0);
|
||||
}
|
||||
}
|
||||
200
uni_modules/tdesign-uniapp/components/watermark/watermark.vue
Normal file
200
uni_modules/tdesign-uniapp/components/watermark/watermark.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<view
|
||||
:class="classPrefix + ' ' + tClass"
|
||||
:style="tools._style([customStyle])"
|
||||
>
|
||||
<block v-if="content">
|
||||
{{ content }}
|
||||
</block>
|
||||
<slot name="content" />
|
||||
<slot />
|
||||
<canvas
|
||||
:id="canvasId"
|
||||
:canvas-id="canvasId"
|
||||
type="2d"
|
||||
:style="canvasStyle"
|
||||
/>
|
||||
<view
|
||||
:class="movable ? 'watermark-move' : ''"
|
||||
:style="tools._style(watermarkStyle)"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tools from '../common/utils.wxs';
|
||||
import watermarkProps from './props';
|
||||
import { prefix } from '../common/config';
|
||||
import { uniComponent } from '../common/src/index';
|
||||
import { appBaseInfo, nextTick } from '../common/utils';
|
||||
import generateBase64Url from './utils/generateBase64Url';
|
||||
import randomMovingStyle from './utils/randomMovingStyle';
|
||||
|
||||
const name = `${prefix}-watermark`;
|
||||
|
||||
export default uniComponent({
|
||||
name,
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
externalClasses: [`${prefix}-class`],
|
||||
props: {
|
||||
...watermarkProps,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
classPrefix: name,
|
||||
tools,
|
||||
watermarkStyle: {},
|
||||
initialed: false,
|
||||
canvasId: `watermark-canvas-${Math.random().toString(36)
|
||||
.slice(2, 11)}`,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canvasStyle() {
|
||||
let result = 'width: 100px; height: 100px;';
|
||||
let shouldHide = true;
|
||||
|
||||
// #ifdef APP-PLUS || MP-ALIPAY
|
||||
if (!this.initialed) {
|
||||
shouldHide = false;
|
||||
}
|
||||
// #endif
|
||||
|
||||
if (shouldHide) {
|
||||
result += 'display: none;';
|
||||
}
|
||||
return result;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
watermarkContent: 'renderWatermark',
|
||||
movable: 'renderWatermark',
|
||||
rotate: 'renderWatermark',
|
||||
x: 'renderWatermark',
|
||||
y: 'renderWatermark',
|
||||
width: 'renderWatermark',
|
||||
height: 'renderWatermark',
|
||||
alpha: 'renderWatermark',
|
||||
lineSpace: 'renderWatermark',
|
||||
moveInterval: 'renderWatermark',
|
||||
zIndex: 'renderWatermark',
|
||||
offset: 'renderWatermark',
|
||||
removable: 'renderWatermark',
|
||||
isRepeat: 'renderWatermark',
|
||||
layout: 'renderWatermark',
|
||||
},
|
||||
async mounted() {
|
||||
await nextTick();
|
||||
this.renderWatermark();
|
||||
},
|
||||
methods: {
|
||||
watermarkColor() {
|
||||
return appBaseInfo.theme === 'dark'
|
||||
? 'rgba(238, 238, 238, 0.1)'
|
||||
: 'rgba(0, 0, 0, 0.1)';
|
||||
},
|
||||
renderWatermark() {
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
|
||||
query
|
||||
.select(`#${this.canvasId}`)
|
||||
.fields({ node: true, size: true })
|
||||
.exec(async (res) => {
|
||||
if (!res[0]?.node) {
|
||||
console.error('Canvas node not found');
|
||||
return;
|
||||
}
|
||||
const canvas = res[0].node;
|
||||
const gapX = this.movable ? 0 : this.x;
|
||||
const gapY = this.movable ? 0 : this.y;
|
||||
const offset = this.offset || [];
|
||||
const offsetLeft = offset[0] || gapX / 2;
|
||||
const offsetTop = offset[1] || gapY / 2;
|
||||
|
||||
const bgImageOptions = {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
rotate: this.movable ? 0 : this.rotate,
|
||||
lineSpace: this.lineSpace,
|
||||
alpha: this.alpha,
|
||||
gapX,
|
||||
gapY,
|
||||
watermarkContent: this.watermarkContent,
|
||||
offsetLeft,
|
||||
offsetTop,
|
||||
watermarkColor: this.watermarkColor(),
|
||||
layout: this.layout,
|
||||
};
|
||||
generateBase64Url.call(
|
||||
this,
|
||||
canvas,
|
||||
this.canvasId,
|
||||
bgImageOptions,
|
||||
(base64Url, backgroundSize) => {
|
||||
let animationVars = {};
|
||||
if (this.movable) {
|
||||
const {
|
||||
left0,
|
||||
left25,
|
||||
left50,
|
||||
left75,
|
||||
top0,
|
||||
top25,
|
||||
top50,
|
||||
top75,
|
||||
} = randomMovingStyle();
|
||||
animationVars = {
|
||||
'--watermark-left-0': left0,
|
||||
'--watermark-left-25': left25,
|
||||
'--watermark-left-50': left50,
|
||||
'--watermark-left-75': left75,
|
||||
'--watermark-top-0': top0,
|
||||
'--watermark-top-25': top25,
|
||||
'--watermark-top-50': top50,
|
||||
'--watermark-top-75': top75,
|
||||
'--watermark-animation-duration': `${
|
||||
(this.moveInterval * 4) / 60
|
||||
}s`,
|
||||
};
|
||||
}
|
||||
|
||||
this.watermarkStyle = {
|
||||
zIndex: this.zIndex,
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundSize: `${
|
||||
backgroundSize?.width || gapX + this.width
|
||||
}px`,
|
||||
pointerEvents: 'none',
|
||||
backgroundRepeat: this.movable ? 'no-repeat' : 'repeat',
|
||||
backgroundImage: `url('${base64Url}')`,
|
||||
...animationVars,
|
||||
};
|
||||
},
|
||||
() => {
|
||||
this.initialed = true;
|
||||
},
|
||||
).catch((e) => {
|
||||
console.log('e', e);
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import './watermark.css';
|
||||
|
||||
/* diff with td-mini */
|
||||
.watermark-move {
|
||||
animation: watermark var(--watermark-animation-duration, 100s) linear infinite;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user