效果图展示
Watermark
参数 说明 类型 默认值 版本 width 水印的宽度,content
的默认值为自身的宽度 number 120 height 水印的高度,content
的默认值为自身的高度 number 64 rotate 水印绘制时,旋转的角度,单位 °
number -22 zIndex 追加的水印元素的 z-index number 9 image 图片源,建议导出 2 倍或 3 倍图,优先级高 string - content 水印文字内容 string | string[] - font 文字样式 Font Font gap 水印之间的间距 [number, number] [100, 100] offset 水印距离容器左上角的偏移量,默认为 gap/2
[number, number] [gap[0]/2, gap[1]/2]
Font
参数 说明 类型 默认值 版本 color 字体颜色 string rgba(0,0,0,.15) fontSize 字体大小 number 16 fontWeight 字体粗细 normal
| light
| weight
| numbernormal fontFamily 字体类型 string sans-serif fontStyle 字体样式 none
| normal
| italic
| oblique
normal
使用Watermark 组件
< Watermark : content= "['watermark', 'Happy Working']" : width= "130" : gap= "[50, 50]" >
< div style= "width: 500px; height: 500px; border: 1px" > < / div>
< / Watermark>
Watermark 组件
< ! --
* Copyright ©
* #
* @author: zw
* @date: 2023 - 05 - 09
-- >
< template>
< div class = "watermark-container" >
< div class = "watermark-content" >
< slot> < / slot>
< / div>
< / div>
< / template>
< script>
let watermarkRef = null
const BaseSize = 2
const FontGap = 3
const getPixelRatio = ( ) => window. devicePixelRatio || 1
const toLowercaseSeparator = ( key ) => key. replace ( / ([A-Z]) / g , '-$1' ) . toLowerCase ( )
const getStyleStr = ( style ) =>
Object. keys ( style)
. map ( ( key ) => ` ${ toLowercaseSeparator ( key) } : ${ style[ key] } ; ` )
. join ( ' ' )
function reRendering ( mutation, watermarkElement ) {
let flag = false
if ( mutation. removedNodes. length) {
flag = Array. from ( mutation. removedNodes) . some ( ( node ) => node === watermarkElement)
}
if ( mutation. type === 'attributes' && mutation. target === watermarkElement) {
flag = true
}
return flag
}
export default {
name : 'Watermark' ,
data ( ) {
return {
stopObservation : false ,
observe : null ,
}
} ,
props : {
zIndex : { type : Number, default : 9 } ,
rotate : { type : Number, default : - 22 } ,
width : { type : [ String, Number] , default : 120 } ,
height : { type : [ String, Number] , default : 64 } ,
image : { type : String, default : '' } ,
content : { type : [ String, Array] , default : '' } ,
font : {
type : Object,
default : ( ) => ( {
fontSize : 16 ,
fontFamily : 'sans-serif' ,
fontStyle : 'normal' ,
fontWeight : 'normal' ,
color : 'rgba(0, 0, 0, 0.15)' ,
} ) ,
} ,
rootClassName : '' ,
gap : { type : Array, default : ( ) => [ 100 , 100 ] } ,
offset : { type : Array, default : ( ) => [ 50 , 50 ] } ,
} ,
mounted ( ) {
function onMutate ( records ) {
if ( this . stopObservation) return
records. forEach ( ( mutation ) => {
if ( ! reRendering ( mutation, watermarkRef) ) return
this . destroyWatermark ( )
this . renderWatermark ( )
} )
}
this . renderWatermark ( )
this . observe = this . useMutationObserver ( this . $el, onMutate . bind ( this ) , { attributes : true , childList : true , subtree : true } )
} ,
methods : {
useMutationObserver ( target, callback, options ) {
const isSupported = typeof MutationObserver !== 'undefined'
if ( ! isSupported) return false
const observe = new MutationObserver ( callback)
observe. observe ( target, options)
return observe
} ,
getMarkSize ( ctx ) {
const props = this . $props
const { fontSize, fontFamily } = props. font
let defaultWidth
let defaultHeight
const content = props. content
const image = props. image
const width = props. width
const height = props. height
if ( ! image && ctx. measureText) {
ctx. font = ` ${ Number ( fontSize) } px ${ fontFamily} `
const contents = Array. isArray ( content) ? content : [ content]
const widths = contents. map ( ( item ) => ctx. measureText ( item) . width)
defaultWidth = Math. ceil ( Math. max ( ... widths) )
defaultHeight = Number ( fontSize. value) * contents. length + ( contents. length - 1 ) * FontGap
}
return [ width ?? defaultWidth, height ?? defaultHeight]
} ,
rotateWatermark ( ctx, rotateX, rotateY, rotate ) {
ctx. translate ( rotateX, rotateY)
ctx. rotate ( ( Math. PI / 180 ) * Number ( rotate) )
ctx. translate ( - rotateX, - rotateY)
} ,
fillTexts ( ctx, drawX, drawY, drawWidth, drawHeight ) {
const props = this . $props
const { fontSize, fontFamily, fontStyle, fontWeight, color } = props. font
const ratio = getPixelRatio ( )
const content = props. content
const mergedFontSize = Number ( fontSize) * ratio
ctx. font = ` ${ fontStyle} normal ${ fontWeight} ${ mergedFontSize} px/ ${ drawHeight} px ${ fontFamily} `
ctx. fillStyle = color
ctx. textAlign = 'center'
ctx. textBaseline = 'top'
ctx. translate ( drawWidth / 2 , 0 )
const contents = Array. isArray ( content) ? content : [ content]
contents?. forEach ( ( item, index ) => {
ctx. fillText ( item ?? '' , drawX, drawY + index * ( mergedFontSize + FontGap * ratio) )
} )
} ,
appendWatermark ( base64Url, markWidth ) {
if ( ! watermarkRef) return
const props = this . $props
const [ gapX, gapY] = props. gap
this . stopObservation = true
const attrs = getStyleStr ( { ... this . markStyle, backgroundImage : ` url(' ${ base64Url} ') ` , backgroundSize : ` ${ ( gapX + markWidth) * BaseSize} px ` } )
watermarkRef. setAttribute ( 'style' , attrs)
this . $el. append ( watermarkRef)
setTimeout ( ( ) => {
this . stopObservation = false
} )
} ,
renderWatermark ( ) {
const props = this . $props
const [ gapX, gapY] = props. gap
const canvas = document. createElement ( 'canvas' )
const ctx = canvas. getContext ( '2d' )
const image = props. image
const rotate = props. rotate
if ( ! ctx) return false
if ( ! watermarkRef) {
watermarkRef = document. createElement ( 'div' )
}
const ratio = getPixelRatio ( )
const [ markWidth, markHeight] = this . getMarkSize ( ctx)
const canvasWidth = ( gapX + markWidth) * ratio
const canvasHeight = ( gapY + markHeight) * ratio
canvas. setAttribute ( 'width' , ` ${ canvasWidth * BaseSize} px ` )
canvas. setAttribute ( 'height' , ` ${ canvasHeight * BaseSize} px ` )
const drawX = ( gapX * ratio) / 2
const drawY = ( gapY * ratio) / 2
const drawWidth = markWidth * ratio
const drawHeight = markHeight * ratio
const rotateX = ( drawWidth + gapX * ratio) / 2
const rotateY = ( drawHeight + gapY * ratio) / 2
const alternateDrawX = drawX + canvasWidth
const alternateDrawY = drawY + canvasHeight
const alternateRotateX = rotateX + canvasWidth
const alternateRotateY = rotateY + canvasHeight
ctx. save ( )
this . rotateWatermark ( ctx, rotateX, rotateY, rotate)
if ( image) {
const img = new Image ( )
img. onload = ( ) => {
ctx. drawImage ( img, drawX, drawY, drawWidth, drawHeight)
ctx. restore ( )
this . rotateWatermark ( ctx, alternateRotateX, alternateRotateY, rotate)
ctx. drawImage ( img, alternateDrawX, alternateDrawY, drawWidth, drawHeight)
this . appendWatermark ( canvas. toDataURL ( ) , markWidth)
}
img. crossOrigin = 'anonymous'
img. referrerPolicy = 'no-referrer'
img. src = image
} else {
this . fillTexts ( ctx, drawX, drawY, drawWidth, drawHeight)
ctx. restore ( )
this . rotateWatermark ( ctx, alternateRotateX, alternateRotateY, rotate)
this . fillTexts ( ctx, alternateDrawX, alternateDrawY, drawWidth, drawHeight)
this . appendWatermark ( canvas. toDataURL ( ) , markWidth)
}
} ,
destroyWatermark ( ) {
if ( ! watermarkRef) return
watermarkRef. remove ( )
watermarkRef = undefined
} ,
} ,
computed : {
markStyle ( ) {
const props = this . $props
const [ gapX, gapY] = props. gap
const [ offsetX, offsetY] = props. offset
const gapXCenter = gapX / 2
const gapYCenter = gapY / 2
const offsetTop = offsetY || gapYCenter
const offsetLeft = offsetX || gapXCenter
const markStyle = {
zIndex : this . zIndex,
position : 'absolute' ,
left : 0 ,
top : 0 ,
width : '100%' ,
height : '100%' ,
pointerEvents : 'none' ,
backgroundRepeat : 'repeat' ,
}
let positionLeft = offsetLeft - gapXCenter
let positionTop = offsetTop - gapYCenter
if ( positionLeft > 0 ) {
markStyle. left = ` ${ positionLeft} px `
markStyle. width = ` calc(100% - ${ positionLeft} px) `
positionLeft = 0
}
if ( positionTop > 0 ) {
markStyle. top = ` ${ positionTop} px `
markStyle. height = ` calc(100% - ${ positionTop} px) `
positionTop = 0
}
markStyle. backgroundPosition = ` ${ positionLeft} px ${ positionTop} px `
return markStyle
} ,
} ,
beforeDestroy ( ) {
this . destroyWatermark ( )
this . observe = null
} ,
}
< / script>
< style lang= "scss" scoped>
. watermark- container {
position : relative;
}
< / style>