acud
Version:
acg react 组件库
247 lines • 9.91 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
/* eslint-disable no-undef */
import React, { useEffect, useImperativeHandle, forwardRef } from 'react';
import classNames from 'classnames';
import WatermarkContext from './context';
import useClips from './useClips';
import useRafDebounce from './useRafDebounce';
import useWatermark from './useWatermark';
import { getPixelRatio, reRendering } from './utils';
import useMutateObserver from './hooks/useMutateObserver';
/**
* Only return `next` when size changed.
* This is only used for elements compare, not a shallow equal!
*/
function getSizeDiff(prev, next) {
return prev.size === next.size ? prev : next;
}
var DEFAULT_GAP_X = 100;
var DEFAULT_GAP_Y = 100;
var Watermark = function Watermark(props, ref) {
var _a, _b;
var _props$zIndex = props.zIndex,
zIndex = _props$zIndex === void 0 ? 9 : _props$zIndex,
_props$rotate = props.rotate,
rotate = _props$rotate === void 0 ? -22 : _props$rotate,
width = props.width,
height = props.height,
image = props.image,
content = props.content,
_props$font = props.font,
font = _props$font === void 0 ? {} : _props$font,
style = props.style,
className = props.className,
rootClassName = props.rootClassName,
_props$gap = props.gap,
gap = _props$gap === void 0 ? [DEFAULT_GAP_X, DEFAULT_GAP_Y] : _props$gap,
offset = props.offset,
children = props.children,
propsContainer = props.container,
_props$inherit = props.inherit,
inherit = _props$inherit === void 0 ? true : _props$inherit;
var _font$color = font.color,
color = _font$color === void 0 ? '#ccc' : _font$color,
_font$fontSize = font.fontSize,
fontSize = _font$fontSize === void 0 ? 14 : _font$fontSize,
_font$fontGap = font.fontGap,
fontGap = _font$fontGap === void 0 ? 3 : _font$fontGap,
_font$alpha = font.alpha,
alpha = _font$alpha === void 0 ? 1 : _font$alpha,
_font$fontWeight = font.fontWeight,
fontWeight = _font$fontWeight === void 0 ? 'normal' : _font$fontWeight,
_font$fontStyle = font.fontStyle,
fontStyle = _font$fontStyle === void 0 ? 'normal' : _font$fontStyle,
_font$fontFamily = font.fontFamily,
fontFamily = _font$fontFamily === void 0 ? 'sans-serif' : _font$fontFamily,
_font$textAlign = font.textAlign,
textAlign = _font$textAlign === void 0 ? 'center' : _font$textAlign;
var _gap = _slicedToArray(gap, 2),
_gap$ = _gap[0],
gapX = _gap$ === void 0 ? DEFAULT_GAP_X : _gap$,
_gap$2 = _gap[1],
gapY = _gap$2 === void 0 ? DEFAULT_GAP_Y : _gap$2;
var gapXCenter = gapX / 2;
var gapYCenter = gapY / 2;
var offsetLeft = (_a = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _a !== void 0 ? _a : gapXCenter;
var offsetTop = (_b = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _b !== void 0 ? _b : gapYCenter;
var markStyle = React.useMemo(function () {
var mergedStyle = {
zIndex: zIndex,
position: 'absolute',
left: 0,
top: 0,
width: '100%',
height: '100%',
pointerEvents: 'none',
backgroundRepeat: 'repeat'
};
/** Calculate the style of the offset */
var positionLeft = offsetLeft - gapXCenter;
var positionTop = offsetTop - gapYCenter;
if (positionLeft > 0) {
mergedStyle.left = "".concat(positionLeft, "px");
mergedStyle.width = "calc(100% - ".concat(positionLeft, "px)");
positionLeft = 0;
}
if (positionTop > 0) {
mergedStyle.top = "".concat(positionTop, "px");
mergedStyle.height = "calc(100% - ".concat(positionTop, "px)");
positionTop = 0;
}
mergedStyle.backgroundPosition = "".concat(positionLeft, "px ").concat(positionTop, "px");
return mergedStyle;
}, [zIndex, offsetLeft, gapXCenter, offsetTop, gapYCenter]);
var _React$useState = React.useState(propsContainer),
_React$useState2 = _slicedToArray(_React$useState, 2),
container = _React$useState2[0],
setContainer = _React$useState2[1];
// Used for nest case like Modal, Drawer
var _React$useState3 = React.useState(new Set()),
_React$useState4 = _slicedToArray(_React$useState3, 2),
subElements = _React$useState4[0],
setSubElements = _React$useState4[1];
// Nest elements should also support watermark
var targetElements = React.useMemo(function () {
var list = container ? [container] : [];
return [].concat(list, _toConsumableArray(Array.from(subElements)));
}, [container, subElements]);
// ============================ Content =============================
/**
* Get the width and height of the watermark. The default values are as follows
* Image: [120, 64]; Content: It's calculated by content;
*/
var getMarkSize = function getMarkSize(ctx) {
var defaultWidth = 120;
var defaultHeight = 64;
if (!image && ctx.measureText) {
ctx.font = "".concat(Number(fontSize), "px ").concat(fontFamily);
var contents = Array.isArray(content) ? content : [content];
var sizes = contents.map(function (item) {
var metrics = ctx.measureText(item);
return [metrics.width, metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent];
});
defaultWidth = Math.ceil(Math.max.apply(Math, _toConsumableArray(sizes.map(function (size) {
return size[0];
}))));
defaultHeight = Math.ceil(Math.max.apply(Math, _toConsumableArray(sizes.map(function (size) {
return size[1];
})))) * contents.length + (contents.length - 1) * fontGap;
}
return [width !== null && width !== void 0 ? width : defaultWidth, height !== null && height !== void 0 ? height : defaultHeight];
};
var getClips = useClips();
var _React$useState5 = React.useState(null),
_React$useState6 = _slicedToArray(_React$useState5, 2),
watermarkInfo = _React$useState6[0],
setWatermarkInfo = _React$useState6[1];
// Generate new Watermark content
var renderWatermark = function renderWatermark() {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
if (ctx) {
var ratio = getPixelRatio();
var _getMarkSize = getMarkSize(ctx),
_getMarkSize2 = _slicedToArray(_getMarkSize, 2),
markWidth = _getMarkSize2[0],
markHeight = _getMarkSize2[1];
var drawCanvas = function drawCanvas(drawContent) {
var _getClips = getClips(drawContent || '', rotate, ratio, markWidth, markHeight, {
color: color,
fontSize: fontSize,
fontGap: fontGap,
alpha: alpha,
fontStyle: fontStyle,
fontWeight: fontWeight,
fontFamily: fontFamily,
textAlign: textAlign
}, gapX, gapY),
_getClips2 = _slicedToArray(_getClips, 2),
nextClips = _getClips2[0],
clipWidth = _getClips2[1];
setWatermarkInfo([nextClips, clipWidth]);
};
if (image) {
var img = new Image();
img.onload = function () {
drawCanvas(img);
};
img.onerror = function () {
drawCanvas(content);
};
img.crossOrigin = 'anonymous';
img.referrerPolicy = 'no-referrer';
img.src = image;
} else {
drawCanvas(content);
}
}
};
var syncWatermark = useRafDebounce(renderWatermark);
// ============================= Effect =============================
// Append watermark to the container
var _useWatermark = useWatermark(markStyle),
_useWatermark2 = _slicedToArray(_useWatermark, 3),
appendWatermark = _useWatermark2[0],
removeWatermark = _useWatermark2[1],
isWatermarkEle = _useWatermark2[2];
useEffect(function () {
if (watermarkInfo) {
targetElements.forEach(function (holder) {
appendWatermark(watermarkInfo[0], watermarkInfo[1], holder);
});
}
}, [watermarkInfo, targetElements]);
// ============================ Observe =============================
var onMutate = function onMutate(mutations) {
mutations.forEach(function (mutation) {
if (reRendering(mutation, isWatermarkEle)) {
syncWatermark();
}
});
};
useMutateObserver(targetElements, onMutate);
useEffect(syncWatermark, [rotate, zIndex, width, height, image, content, color, fontSize, fontGap, alpha, fontWeight, fontStyle, fontFamily, textAlign, gapX, gapY, offsetLeft, offsetTop]);
// ============================ Context =============================
var watermarkContext = React.useMemo(function () {
return {
add: function add(ele) {
setSubElements(function (prev) {
var clone = new Set(prev);
clone.add(ele);
return getSizeDiff(prev, clone);
});
},
remove: function remove(ele) {
removeWatermark(ele);
setSubElements(function (prev) {
var clone = new Set(prev);
clone["delete"](ele);
return getSizeDiff(prev, clone);
});
}
};
}, []);
// ============================= Render =============================
var childNode = inherit ? /*#__PURE__*/React.createElement(WatermarkContext.Provider, {
value: watermarkContext
}, children) : children;
useImperativeHandle(ref, function () {
return watermarkContext;
});
if (propsContainer) {
return /*#__PURE__*/React.createElement(React.Fragment, null);
}
return /*#__PURE__*/React.createElement("div", {
ref: setContainer,
className: classNames(className, rootClassName),
style: _extends({
position: 'relative'
}, style)
}, childNode);
};
if (process.env.NODE_ENV !== 'production') {
Watermark.displayName = 'Watermark';
}
export default /*#__PURE__*/forwardRef(Watermark);