80 行 JS 代码实现页面添加水印:文字水印、多行文字水印、图片水印、文字&图片水印

简介: 80 行 JS 代码实现页面添加水印:文字水印、多行文字水印、图片水印、文字&图片水印1. 信息标识: 水印可以用于标识文档的所有者、保密级别、状态或其他相关信息,帮助用户更好地理解文档内容的属性。2. 版权保护: 在文档中添加水印可以帮助保护内容的版权,防止他人未经授权地复制、转载或篡改内容。3. 安全保护: 对于敏感信息或机密文档,添加水印可以帮助防止信息泄露,提高文档的安全性。4. 提升专业性: 在一些场景下,如商业报告、合同文件等,添加水印可以增加文档的专业性和正式性。5. 防止截屏或拷贝: 在网页中添加水印可以防止用户通过截屏或复制粘贴等方式非法获取文档内容。

80 行 JS 代码实现页面添加水印:文字水印、多行文字水印、图片水印、文字&图片水印

一、水印概括

1. 添加水印的好处

  1. 信息标识: 水印可以用于标识文档的所有者、保密级别、状态或其他相关信息,帮助用户更好地理解文档内容的属性。
  2. 版权保护: 在文档中添加水印可以帮助保护内容的版权,防止他人未经授权地复制、转载或篡改内容。
  3. 安全保护: 对于敏感信息或机密文档,添加水印可以帮助防止信息泄露,提高文档的安全性。
  4. 提升专业性: 在一些场景下,如商业报告、合同文件等,添加水印可以增加文档的专业性和正式性。
  5. 防止截屏或拷贝: 在网页中添加水印可以防止用户通过截屏或复制粘贴等方式非法获取文档内容。

2. 添加水印的坏处

  1. 视觉干扰: 过多或过显眼的水印可能会影响页面的美观度和可读性,甚至干扰用户对页面内容的浏览和理解。
  2. 性能影响: 在页面中添加水印可能会增加页面加载时间和资源消耗,特别是对于大型或复杂的水印图案或动态水印。
  3. 用户体验: 一些用户可能会认为水印对页面内容的查看和操作产生不必要的干扰,从而降低他们的使用体验。
  4. 不适用于移动端: 在移动设备上,屏幕空间有限,添加水印可能会占用宝贵的屏幕空间,影响用户的阅读和操作。

二、技术方案

1. watermark 第三方库

优势:已有 github 库,可直接使用
劣势:

  • 库比较老,多年未更新
  • 只支持图片水印,不支持文案水印
  • 不支持多行水印
  • 包比较重,增加 SDK 负担

2. JS 简单实现水印功能

优势:

  • 可支持图片和文案水印
  • 封装函数即可,轻量
    劣势:需要自己封装

三、水印功能实现

1. 水印功能需求

  1. 需要支持文案和图片
  2. 可设置文案颜色字体等样式
  3. 可支持多行文字水印
  4. 可动态控制水印间隔
  5. 能铺满全屏
  6. 可旋转
  7. 不影响页面其他模块背景展示

2. 功能实现

/**
 * 水印功能代码实现
 */
interface IWarterMarkProps{
  text?: string | string[]  // 水印文案
  imgUrl?: string           // 水印图片
  imageOpacity?: number     // 图片透明度
  fontStyle?: string        // 字体样式
  fontVariant?: string      // 字体变体
  fontWeight?: number       // 字体粗细
  fontFamily?: string       // 字体
  fontSize?: number         // 字体大小
  lineHeight?: number       // 字体行高
  fontColor?: string        // 字体颜色
  rotate?: number           // 水印旋转角度 deg
  canvasRotate?: number     // canvas 旋转角度 deg
  imageWidth?: number       // 图片宽度
  imageHeight?: number      // 图片高度
  xOffset?: number          // 水印 x 偏移
  yOffset?: number          // 水印 y 偏移
  width?: number            // 宽度
  height?: number           // 高度
  zIndex: number            // 层级
}
interface ISetDomProps{
  url: string               // 图片 URL
  rotate?: number           // 水印旋转角度
  zIndex?: number           // 层级
  width?: number            // 宽度
}
/**
 * 设置 dom CSS 属性
 * @param {ISetDomProps} props dom 数据类型
 */
const setDomCSSAttr = (props: ISetDomProps) => {
  const {
    url, rotate = 0, zIndex = 10, width = 32
  } = props
  let waterMarker = document.getElementById('water-marker')
  let isInitDom = true
  if (!waterMarker) {
    isInitDom = false
    waterMarker = document.createElement('div')
    waterMarker.id = 'water-marker'
  }
  Object.assign(waterMarker.style, {
    transform: rotate ? `rotate(${rotate}deg)` : '',
    backgroundSize: `${width}px`,
    backgroundImage: `url(${url}), url(${url})`,
    backgroundRepeat: 'repeat',
    position: 'fixed',
    left: 0,
    top: 0,
    zIndex,
    width: '100vw',
    height: '100vh',
    pointerEvents: 'none',
    mixBlendMode: 'multiply'
  });
  !isInitDom && document.body.appendChild(waterMarker)
}
/**
 * 获取 canvas 像素比
 * @param {CanvasRenderingContext2D} context 
 * @returns 像素比
 */
const getRatio = (context: any) => {
  if (!context) {
    return 1;
  }
  const backingStore = context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1
  return (window.devicePixelRatio || 1) / backingStore
}
/**
 * 设置水印
 * @param {IWarterMarkProps} props 水印各种属性
 */
const setWarterMarker = (props: IWarterMarkProps) => {
  const {
    imgUrl = '', imageOpacity = 1, imageWidth = 0, imageHeight = 0, fontColor = 'rgba(128, 128, 128, .2)',
    fontVariant = 'normal', fontWeight = 400, fontSize = 14, lineHeight = 14, fontStyle = 'normal', fontFamily = 'arial', rotate = 0, canvasRotate = 0,
    yOffset = 0, xOffset = 0, width = 200, height = 200, zIndex = 10,
  } = props
  const text = typeof props.text === 'object' ? props.text : props.text ? [props.text] : []
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
  const ratio = getRatio(ctx);
  const canvasWidth = width * ratio
  const canvasHeight = height * ratio
  const canvasOffsetLeft = xOffset * ratio
  const canvasOffsetTop = yOffset * ratio
  canvas.width = canvasWidth
  canvas.height = canvasHeight
  ctx.rotate(canvasRotate * (Math.PI / 180));
  let url = ''
  if (text.length) {
    ctx.font = `${fontStyle} ${fontVariant} ${fontWeight} ${fontSize * ratio}px/${lineHeight * ratio}px ${fontFamily || getComputedStyle(document.body).fontFamily}`
    ctx.fillStyle = fontColor
    text.forEach((content, index) => {
      ctx.fillText(content, canvasOffsetLeft, index * lineHeight + canvasOffsetTop + lineHeight * ratio)
    })
    url = canvas.toDataURL()
  }
  if (imgUrl) {
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.referrerPolicy = 'no-referrer';
    img.src = imgUrl;
    img.onload = () => {
      ctx.globalAlpha = imageOpacity
      ctx.drawImage(img, canvasOffsetLeft, canvasOffsetTop, (imageWidth || (imageHeight ? img.width * imageHeight / img.height : img.width)) * ratio, (imageHeight || (imageWidth ? img.height * imageWidth / img.width : img.height)) * ratio)
      setDomCSSAttr({
        url: canvas.toDataURL(),
        rotate,
        zIndex,
        width
      })
      return
    };
    return
  }
  setDomCSSAttr({
    url,
    rotate,
    zIndex,
    width
  })
}

3. 步骤解析

3.1. setWarterMarker 方法

setWarterMarker 是使用水印的方法,参数是一个对象,包含以下内容

interface IWarterMarkProps{
  text?: string | string[]  // 水印文案
  imgUrl?: string           // 水印图片
  imageOpacity?: number     // 图片透明度
  fontStyle?: string        // 字体样式
  fontVariant?: string      // 字体变体
  fontWeight?: number       // 字体粗细
  fontFamily?: string       // 字体
  fontSize?: number         // 字体大小
  lineHeight?: number       // 字体行高
  fontColor?: string        // 字体颜色
  rotate?: number           // 水印旋转角度 deg
  canvasRotate?: number     // canvas 旋转角度 deg
  imageWidth?: number       // 图片宽度
  imageHeight?: number      // 图片高度
  xOffset?: number          // 水印 x 偏移
  yOffset?: number          // 水印 y 偏移
  width?: number            // 宽度
  height?: number           // 高度
  zIndex: number            // 层级
}
  1. 获取各个参数值
  2. 创建 canvas,获取 canvas 的像素比,设置 canvas 的各种参数
  3. 如果存在 text 属性,则进行遍历绘制
  4. 如果有图片,则创建 image,再进行绘制
  5. 生成 url,通过 setDomCSSAttr 方法进行 dom 渲染

3.2. getRatio 方法

获取 canvas 的像素比,更清晰的绘制 canvas

3.3. setDomCSSAttr 方法

接收一个对象,包含以下内容

interface ISetDomProps{
  url: string               // 图片 URL
  rotate?: number           // 水印旋转角度
  zIndex?: number           // 层级
  width?: number            // 宽度
}
  1. 查找/创建 dom 元素
  2. 设置 style 属性
  • transform: 旋转
  • backgroundSize:背景大小
  • backgroundImage:背景图片
  • backgroundRepeat:背景重复
  • position:定位
  • zIndex:层级
  • pointerEvents:不能被选中
  • mixBlendMode:控制元素的混合模式

3.4. CSS mix-blend-mode 属性

mix-blend-modeCSS 中的一个属性,用于控制元素的混合模式。它可以让元素的背景色和内容色以一种特定的方式混合在一起,从而产生各种视觉效果。
以下是一些常用的 mix-blend-mode 值:

  1. normal:默认值,内容色完全覆盖背景色。
  2. multiply:将内容色与背景色相乘。通常用于创建暗色效果。
  3. screen:将内容色与背景色相除,并取反。通常用于创建亮色效果。
  4. overlay:结合了 multiplyscreen 模式,根据背景色的亮度来决定内容色的混合模式。
  5. darken:内容色和背景色中的每个像素值,取对应像素值的较小者。
  6. lighten:内容色和背景色中的每个像素值,取对应像素值的较大者。
  7. color-dodge:颜色减淡,通过减少背景色的值来增加内容色的亮度。
  8. color-burn:颜色加深,通过增加背景色的值来降低内容色的亮度。
  9. difference:计算内容色和背景色之间的差异,产生颜色反转的效果。
  10. exclusion:将内容色和背景色相减,并添加其结果的两倍。
  11. hue:保留内容色的色相,并使用背景色的饱和度和亮度。
  12. saturation:保留内容色的饱和度,并使用背景色的色相和亮度。
  13. color:保留内容色的色相和饱和度,并使用背景色的亮度。
  14. luminosity:保留内容色的亮度,并使用背景色的色相和饱和度。

使用 mix-blend-mode 属性,可以轻松地创建各种视觉效果,例如颜色混合、遮罩效果等。

三、水印使用

1. 文字

setWarterMarker({
  text: '这是水印, 这是水印, 这是水印',
  width: 200,
  height: 200,
  fontColor: 'red',
  fontSize: 8,
  lineHeight: 10,
})

2. 图片

setWarterMarker({
  imgUrl: 'https://sponsors.vuejs.org/images/chrome_frameworks_fund.png',
  imageHeight: 50,
  imageWidth: 50,
  imageOpacity: 0.1,
})

3. 图片和文字结合

setWarterMarker({
  text: '这是水印, 这是水印, 这是水印',
  width: 200,
  height: 200,
  fontColor: 'red',
  fontSize: 8,
  lineHeight: 10,
  imgUrl: 'https://sponsors.vuejs.org/images/chrome_frameworks_fund.png',
  imageHeight: 50,
  imageWidth: 50,
  imageOpacity: 0.1,
  canvasRotate: 15
})

4. 多行水印和图片

setWarterMarker({
  text: ['这是水印, 这是水印, 这是水印', '第二行水印', '第三行水印', '第四行水印'],
  width: 200,
  height: 200,
  fontColor: 'red',
  fontSize: 8,
  lineHeight: 40,
  imgUrl: 'https://sponsors.vuejs.org/images/chrome_frameworks_fund.png',
  imageHeight: 50,
  imageWidth: 50,
  imageOpacity: 0.1,
})

四、总结

  1. 水印技术调研显示,水印在保护版权、信息标识和安全保护方面具有重要作用。
  2. 通过CSS、JavaScript或第三方库,可以实现页面水印功能。
  3. 过多或显眼的水印可能影响用户体验,而性能和兼容性也是考虑的因素。
  4. 综合评估水印的利弊,可结合实际需求谨慎选择适合的实现方式。
  5. 如果需求有更复杂的水印,可以按步骤去改即可。
目录
相关文章
|
3天前
|
JavaScript
JS代码动态打印404页面源码
源码由HTML+CSS+JS组成,记事本打开源码文件可以进行内容文字之类的修改,双击html文件可以本地运行效果,也可以上传到服务器里面,重定向这个界面
6 0
JS代码动态打印404页面源码
|
11天前
|
前端开发 JavaScript
js 等待接口访问成功后执行指定代码【3种方法】(含async await Promise的使用)
js 等待接口访问成功后执行指定代码【3种方法】(含async await Promise的使用)
8 1
|
3天前
|
测试技术 UED
断网之后的页面,Autox.js是点击还是上下滑动比较好?
断网之后的页面,Autox.js是点击还是上下滑动比较好?
|
3天前
|
测试技术 API Android开发
autox.js如何监听异常情况,比如网络中断、内存慢、应用死机或者页面无响应
autox.js如何监听异常情况,比如网络中断、内存慢、应用死机或者页面无响应
|
7天前
|
JavaScript
js 延时执行代码的最佳实践 —— 自定义 sleep 函数
js 延时执行代码的最佳实践 —— 自定义 sleep 函数
10 0
|
8天前
|
JavaScript 前端开发
js【深度解析】代码的执行顺序
js【深度解析】代码的执行顺序
11 0
|
8天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的图片推荐系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的图片推荐系统附带文章源码部署视频讲解等
8 0
|
9天前
|
JavaScript
js 滚动鼠标滑轮放大缩小图片
js 滚动鼠标滑轮放大缩小图片
8 0
|
9天前
|
前端开发 JavaScript
js 打开资源管理器(经典范例:纯前端选择并预览图片)
js 打开资源管理器(经典范例:纯前端选择并预览图片)
20 0
|
9天前
|
JavaScript API
前后端数据交互.js文件的axios的写法,想要往后端发送数据,页面注入API,await的意思是同步等待服务器数据,并返回,axios注入在其他页面,其他页面调用的时候,同步作用
前后端数据交互.js文件的axios的写法,想要往后端发送数据,页面注入API,await的意思是同步等待服务器数据,并返回,axios注入在其他页面,其他页面调用的时候,同步作用