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. 如果需求有更复杂的水印,可以按步骤去改即可。
目录
相关文章
|
13天前
|
JavaScript 前端开发 安全
【逆向】Python 调用 JS 代码实战:使用 pyexecjs 与 Node.js 无缝衔接
本文介绍了如何使用 Python 的轻量级库 `pyexecjs` 调用 JavaScript 代码,并结合 Node.js 实现完整的执行流程。内容涵盖环境搭建、基本使用、常见问题解决方案及爬虫逆向分析中的实战技巧,帮助开发者在 Python 中高效处理 JS 逻辑。
|
3月前
|
JavaScript 前端开发 算法
流量分发代码实战|学会用JS控制用户访问路径
流量分发工具(Traffic Distributor),又称跳转器或负载均衡器,可通过JavaScript按预设规则将用户随机引导至不同网站,适用于SEO优化、广告投放、A/B测试等场景。本文分享一段不到百行的JS代码,实现智能、隐蔽的流量控制,并附完整示例与算法解析。
91 1
|
4月前
|
JavaScript 前端开发
怀孕b超单子在线制作,p图一键生成怀孕,JS代码装逼娱乐
模拟B超单的视觉效果,包含随机生成的胎儿图像、医疗文本信息和医院标志。请注意这仅用于前端开发学习
|
4月前
|
前端开发 JavaScript 容器
制作b超单生成器, 假怀孕b超单图片制作, p图医院证明【css+html+js装逼恶搞神器】
本资源提供一个适合用于熟人之间恶搞的工具,效果逼真,仅供学习参考与娱乐。包含前端技术学习要点:语义化布局、响应式设计、Flexbox、图片自适应
|
4月前
|
JavaScript
JS代码的一些常用优化写法
JS代码的一些常用优化写法
72 0
|
6月前
|
存储 JavaScript 前端开发
在NodeJS中使用npm包进行JS代码的混淆加密
总的来说,使用“javascript-obfuscator”包可以帮助我们在Node.js中轻松地混淆JavaScript代码。通过合理的配置,我们可以使混淆后的代码更难以理解,从而提高代码的保密性。
451 9
|
JavaScript 前端开发 Java
《编写可维护的JavaScript》——1.3 行的长度
《编写可维护的JavaScript》——1.3 行的长度本节书摘来自异步社区《编写可维护的JavaScript》一书中的第1章,第1.3节,作者: 【美】Nicholas C. Zakas 译者: 李晶 , 郭凯 , 张散集 更多章节内容可以访问云栖社区“异步社区”公众号查看。
1461 0
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
241 2
|
11月前
|
JavaScript 前端开发
JavaScript中的原型 保姆级文章一文搞懂
本文详细解析了JavaScript中的原型概念,从构造函数、原型对象、`__proto__`属性、`constructor`属性到原型链,层层递进地解释了JavaScript如何通过原型实现继承机制。适合初学者深入理解JS面向对象编程的核心原理。
162 1
JavaScript中的原型 保姆级文章一文搞懂
|
11月前
JS+CSS3文章内容背景黑白切换源码
JS+CSS3文章内容背景黑白切换源码是一款基于JS+CSS3制作的简单网页文章文字内容背景颜色黑白切换效果。
101 0