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. 如果需求有更复杂的水印,可以按步骤去改即可。
目录
相关文章
|
5天前
|
监控 JavaScript 前端开发
使用Vue.js开发员工上网行为监控的实时数据展示页面
使用Vue.js开发的实时员工上网行为监控页面,展示员工访问的网站、应用和时间等数据。页面响应式设计,适应不同设备。通过Vue组件显示实时数据,如`<li v-for="activity in activities">`循环渲染。数据定时更新,利用Vue的生命周期钩子和axios从服务器获取并自动刷新,确保数据的时效性。该页面有助于管理者即时了解员工网络活动,保障企业网络安全和资源管理。
36 5
|
1天前
|
设计模式 存储 JavaScript
【JavaScript】JavaScript对象设计哲学:八种模式塑造高效代码
【JavaScript】JavaScript对象设计哲学:八种模式塑造高效代码
10 5
|
1天前
|
前端开发 JavaScript 安全
高级前端开发需要知道的 25 个 JavaScript 单行代码
1. 不使用临时变量来交换变量的值 2. 对象解构,让数据访问更便捷 3. 浅克隆对象 4. 合并对象 5. 清理数组 6. 将 NodeList 转换为数组 7. 检查数组是否满足指定条件 8. 将文本复制到剪贴板 9. 删除数组重复项 10. 取两个数组的交集 11. 求数组元素的总和 12. 根据指定条件判断,是否给对象的属性赋值 13. 使用变量作为对象的键 14. 离线状态检查器 15. 离开页面弹出确认对话框 16. 对象数组,根据对象的某个key求对应值的总和 17. 将 url 问号后面的查询字符串转为对象 18. 将秒数转换为时间格式的字符串 19.
9 3
高级前端开发需要知道的 25 个 JavaScript 单行代码
|
1天前
|
JavaScript 前端开发
页面加载时执行特定的 JavaScript 代码
页面加载时执行特定的 JavaScript 代码
|
2天前
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。
|
6天前
|
JavaScript UED
ab77b6ea7f3fbf79.JS代码报错什么原因?
网站出现JS报错,表现为黄色小叹号,经排查发现是360自动收录JS引起。这不仅导致页面延迟增加,还影响用户体验。解决方案是删除360的自动推送JS代码。
10 1
|
前端开发 JavaScript
8 种技巧让你编写更简洁的 JavaScript 代码
8 种技巧让你编写更简洁的 JavaScript 代码
225 0
8 种技巧让你编写更简洁的 JavaScript 代码
|
Web App开发 JavaScript 前端开发
|
Web App开发 JavaScript 前端开发
【译】如何编写避免垃圾开销的实时 JavaScript 代码
本文讲的是【译】如何编写避免垃圾开销的实时 JavaScript 代码,哇,这篇文章已经写了有很长一段时间了,十分感谢那些精彩的回复!其中有一些对于一些技术的指正,如使用 'delete' 。我知道了使用它可能会导致其他的降速问题,因此,我们在引擎中极少使用它。
1127 0