UNPKG

acud

Version:

acg react 组件库

247 lines • 9.91 kB
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);