搜索
首页web前端Vue.js怎么用Vue3指令实现水印背景

页面水印业务相信我们都有遇过,为什么需要给页面添加水印?为了保护自己的版权和知识产权,给图片加上水印一般是为了防止盗图者用于商业用途,损害原作者的权益。那么在我们开发当中有什么方法可以实现呢?一般分为前端实现和后端实现这两种方法,本文主要是学习前端实现方法:

  • 方式一:直接将字体用块元素包裹,动态设置绝对定位,然后通过transform属性旋转。但是需要考虑一个问题,当图片过大或图片过多时会很影响性能,所以就不详细说这一方式了。

  • 方式二:canvas上绘制出字体,设置好样式,最后以图片的样式导出,用图片作为水印层的背景图。

在学习水印层之前,我先抛出两个问题:

  • 如果水印文字长,水印可以实现自适应吗?

  • 能否限制用户修改并删除水印?

其实上面这两个问题是我们做页面水印需要考虑的两个核心问题,好的,话不多说,我们一起带着问题去探索????。

首先定义一个指令,我们要明确两点:命名(v-water-mask)和绑定值(配置值,option),实现如下:

<div v-water-mask:options="wmOption"></div>
// 配置值
const wmOption = reactive<WMOptions>({
  textArr: [&#39;路灯下的光&#39;, `${dayjs().format(&#39;YYYY-MM-DD HH:mm&#39;)}`],
  deg: -35,
});

效果如下图所示:

怎么用Vue3指令实现水印背景

从上图中我们可以看出,文字有文本以及时间字符串,水印文字都是倾斜了一定角度,其实就是旋转了一定角度的。那么问题来了,我们可能问这些是怎么设置的?首先这需要使用指令的时候通过一些配置来实现一些固定值,下面这里都把这些配置都封装成一个类了,为什么要这样做?这样就不用使用的时候每次都要设定一个默认值,比如通过定义接口来引用这些配置时每次都需要设置一个默认值:

export class WMOptions {
  constructor(init?: WMOptions) {
    if (init) {
      Object.assign(this, init);
    }
  }
  textArr: Array<string> = [&#39;test&#39;, &#39;自定义水印&#39;]; // 需要展示的文字,多行就多个元素【必填】
  font?: string = &#39;16px "微软雅黑"&#39;; // 字体样式
  fillStyle?: string = &#39;rgba(170,170,170,0.4)&#39;; // 描边样式
  maxWidth?: number = 200; // 文字水平时最大宽度
  minWidth?: number = 120; // 文字水平时最小宽度
  lineHeight?: number = 24; // 文字行高
  deg?: number = -45; // 旋转的角度 0至-90之间
  marginRight?: number = 120; // 每个水印的右间隔
  marginBottom?: number = 40; // 每个水印的下间隔
  left?: number = 20; // 整体背景距左边的距离
  top?: number = 20; // 整体背景距上边的距离
  opacity?: string = &#39;.75&#39;; // 文字透明度
  position?: &#39;fixed&#39; | &#39;absolute&#39; = &#39;fixed&#39;; // 容器定位方式(值为absolute时,需要指定一个父元素非static定位)
}

细心的地我们可能会发现显示地文本是一个数组,这样主要是为了方便分行,聪明地我们可能会问:假如其中一个比较长怎么换行?,别急别急,我们先了解一下指令是怎么定义的:

定义指令:首先定义为一个ObjectDirective对象类型,因为指令也就是通过在不同生命周期中对当前元素做一些操作。

const WaterMask: ObjectDirective = {
  // el为当前元素
  // bind是当前绑定的属性,注意地,由于是vue3实现,这个值是一个ref类型
    beforeMount(el: HTMLElement, binding: DirectiveBinding) {
        // 实现水印的核心方法
        waterMask(el, binding);
    },
    mounted(el: HTMLElement, binding: DirectiveBinding) {
        nextTick(() => {
          // 禁止修改水印
          disablePatchWaterMask(el);
        });
    },
    beforeUnmount() {
        // 清除监听DOM节点的监听器
        if (observerTemp.value) {
          observerTemp.value.disconnect();
          observerTemp.value = null;
        }
    },
};
export default WaterMask;
  • waterMask方法:实现水印业务细节呈现,对文字的自适应换行,根据页面元素大小来计算合适宽高值。

  • disablePatchWaterMask方法:通过MutationObserver方法监听DOM元素修改,从而阻止用户取消水印的呈现。

声明指令:在main文件中定义声明指令,这样我们就可以全局使用这个指令了

app.directive(&#39;water-mask&#39;, WaterMask);

接下来我们来看一一分析水印的两个核心方法:waterMask和disablePatchWaterMask。

实现水印功能

通过waterMask方法实现,waterMask方法主要是做了四件事情:

let defaultSettings = new WMOptions();
const waterMask = function (element: HTMLElement, binding: DirectiveBinding) {
  // 合并默认值和传参配置
  defaultSettings = Object.assign({}, defaultSettings, binding.value || {});
  defaultSettings.minWidth = Math.min(
    defaultSettings.maxWidth!,
    defaultSettings.minWidth!
  ); // 重置最小宽度
  const textArr = defaultSettings.textArr;
  if (!Util.isArray(textArr)) {
    throw Error(&#39;水印文本必须放在数组中!&#39;);
  }
  const c = createCanvas(); // 动态创建隐藏的canvas
  draw(c, defaultSettings); // 绘制文本
  convertCanvasToImage(c, element); // 转化图像
};

获取配置的默认值:由于开发者传参的时候不一定需要把所有配置的传进来,其实按照本身默认的一些值就行,通过浅拷贝把指令绑定的值传进来的一起融合一起就可以更新默认的配置:

创建canvas标签:因为是通过canvas实现的,我们本身是没有直接在template中呈现这个标签,所以需要通过document对象创建canvas标签:

function createCanvas() {
  const c = document.createElement(&#39;canvas&#39;);
  c.style.display = &#39;none&#39;;
  document.body.appendChild(c);
  return c;
}

绘制文本:首先遍历传入需要显示的水印信息,也就是textArr文本数组,遍历数组判断数组元素是不是超出了配置的每个水印默认宽高,然后根据文本元素返回超出文本长度的文本分割数组,同时把文本最大宽度返回,最后通过切割结果动态修改canvas的宽高。

function draw(c: any, settings: WMOptions) {
  const ctx = c.getContext(&#39;2d&#39;);
  // 切割超过最大宽度的文本并获取最大宽度
  const textArr = settings.textArr || []; // 水印文本数组
  let wordBreakTextArr: Array<any> = [];
  const maxWidthArr: Array<number> = [];
  // 遍历水印文本数组,判断每个元素的长度
  textArr.forEach((text) => {
    const result = breakLinesForCanvas(ctx,text + &#39;&#39;,settings.maxWidth!,settings.font!);
    // 合并超出最大宽度的分割数组
    wordBreakTextArr = wordBreakTextArr.concat(result.textArr);
    // 最大宽度
    maxWidthArr.push(result.maxWidth);
  });
  // 最大宽度排序,最后取最大的最大宽度maxWidthArr[0]
  maxWidthArr.sort((a, b) => {
    return b - a;
  });
  // 根据需要切割结果,动态改变canvas的宽和高
  const maxWidth = Math.max(maxWidthArr[0], defaultSettings.minWidth!);
  const lineHeight = settings.lineHeight!;
  const height = wordBreakTextArr.length * lineHeight;
  const degToPI = (Math.PI * settings.deg!) / 180;
  const absDeg = Math.abs(degToPI);
  // 根据旋转后的矩形计算最小画布的宽高
  const hSinDeg = height * Math.sin(absDeg);
  const hCosDeg = height * Math.cos(absDeg);
  const wSinDeg = maxWidth * Math.sin(absDeg);
  const wCosDeg = maxWidth * Math.cos(absDeg);
  c.width = parseInt(hSinDeg + wCosDeg + settings.marginRight! + &#39;&#39;, 10);
  c.height = parseInt(wSinDeg + hCosDeg + settings.marginBottom! + &#39;&#39;, 10);
  // 宽高重置后,样式也需重置
  ctx.font = settings.font;
  ctx.fillStyle = settings.fillStyle;
  ctx.textBaseline = &#39;hanging&#39;; // 默认是alphabetic,需改基准线为贴着线的方式
  // 移动并旋转画布
  ctx.translate(0, wSinDeg);
  ctx.rotate(degToPI);
  // 绘制文本
  wordBreakTextArr.forEach((text, index) => {
    ctx.fillText(text, 0, lineHeight * index);
  });
}

从上面代码中我们可以看出绘制文本的核心操作是切割超长文本和动态修改canvas的宽高。我们接下来看看这两个操作是如何实现的?

measureText()方法是基于当前字型来计算字符串宽度的。

// 根据最大宽度切割文字
function breakLinesForCanvas(context: any,text: string,width: number,font: string) {
  const result = [];
  let maxWidth = 0;
  if (font) {
    context.font = font;
  }
  // 查找切割点
  let breakPoint = findBreakPoint(text, width, context);
  while (breakPoint !== -1) {
    // 切割点前的元素入栈
    result.push(text.substring(0, breakPoint));
    // 切割点后的元素
    text = text.substring(breakPoint);
    maxWidth = width;
    // 查找切割点后的元素是否还有切割点
    breakPoint = findBreakPoint(text, width, context);
  }
  // 如果切割的最后文本还有文本就push
  if (text) {
    result.push(text);
    const lastTextWidth = context.measureText(text).width;
    maxWidth = maxWidth !== 0 ? maxWidth : lastTextWidth;
  }
  return {
    textArr: result,
    maxWidth: maxWidth,
  };
}
// 寻找切换断点
function findBreakPoint(text: string, width: number, context: any) {
  let min = 0;
  let max = text.length - 1;
  while (min <= max) {
    // 二分字符串中点
    const middle = Math.floor((min + max) / 2);
    // measureText()方法是基于当前字型来计算字符串宽度的
    const middleWidth = context.measureText(text.substring(0, middle)).width;
    const oneCharWiderThanMiddleWidth = context.measureText(
      text.substring(0, middle + 1)
    ).width;
    // 判断当前文本切割是否超了的临界点
    if (middleWidth <= width && oneCharWiderThanMiddleWidth > width) {
      return middle;
    }
    // 如果没超继续遍历查找
    if (middleWidth < width) {
      min = middle + 1;
    } else {
      max = middle - 1;
    }
  }
  return -1;
}

怎么用Vue3指令实现水印背景

所以canvas图形宽为hSinDeg + wCosDeg + settings.marginRight。canvas图形高为:wSinDeg + hCosDeg + settings.marginBottom。

  • 切割超长文本:

  • 寻找切割点:通过二分查找方法查询字符串超长的位置在哪里:

  • 动态修改canvas的宽高:通过旋转的角度值、最大宽度值以及勾股定理一一计算宽度和高度,首先我们需要把旋转的角度转换为弧度值(公式:π/180×角度,也就是 (Math.PI*settings.deg!) / 180 ),我们先看看下图:

转化图像:通过对当前canvas配置转化为图形url,然后配置元素的style属性。

// 将绘制好的canvas转成图片
function convertCanvasToImage(canvas: any, el: HTMLElement) {
  // 判断是否为空渲染器
  if (Util.isUndefinedOrNull(el)) {
    console.error(&#39;请绑定渲染容器&#39;);
  } else {
    // 转化为图形数据的url
    const imgData = canvas.toDataURL(&#39;image/png&#39;);
    const divMask = el;
    divMask.style.cssText = `position: ${defaultSettings.position}; left:0; top:0; right:0; bottom:0; z-index:9999; pointer-events:none;opacity:${defaultSettings.opacity}`;
    divMask.style.backgroundImage = &#39;url(&#39; + imgData + &#39;)&#39;;
    divMask.style.backgroundPosition =
      defaultSettings.left + &#39;px &#39; + defaultSettings.top + &#39;px&#39;;
  }
}

实现禁止用户修改水印

我们都知道,如果用户需要修改html一般都会浏览器调式中的Elements中修改我们网页的元素的样式就可以,也就是我们只要监听到DOM元素被修改就可以,控制修改DOM无法生效。

由于修改DOM有两种方法:修改元素节点和修改元素属性,所以只要控制元素的相关DOM方法中进行相应操作就可以实现我们的禁止。而通过disablePatchWaterMask方法主要做了三件事情:

  • 创建MutationObserver实例:也就是实例化MutationObserver,这样才能调用MutationObserver中的observe函数实现DOM修改的监听。

  • 创建MutationObserver回调函数:通过传入的两个参数,一个当前元素集合和observer监听器。

  • 监听需要监听的元素:调用observer需要传入监听元素以及监听配置,这个可以参考一下MutationObserver用法配置。

function disablePatchWaterMask(el: HTMLElement) {
  // 观察器的配置(需要观察什么变动)
  const config = {
    attributes: true,
    childList: true,
    subtree: true,
    attributeOldValue: true,
  };
  /* MutationObserver 是一个可以监听DOM结构变化的接口。 */
  const MutationObserver =
    window.MutationObserver || window.WebKitMutationObserver;
  // 当观察到变动时执行的回调函数
  const callback = function (mutationsList: any, observer: any) {
    console.log(mutationsList);
    for (let mutation of mutationsList) {
      let type = mutation.type;
      switch (type) {
        case &#39;childList&#39;:
          if (mutation.removedNodes.length > 0) {
            // 删除节点,直接从删除的节点数组中添加回来
            mutation.target.append(mutation.removedNodes[0]);
          }
          break;
        case &#39;attributes&#39;:
          // 为什么是这样处理,我们看一下下面两幅图
          mutation.target.setAttribute(&#39;style&#39;, mutation.target.oldValue);
          break;
        default:
          break;
      }
    }
  };
  // 创建一个观察器实例并传入回调函数
  const observer = new MutationObserver(callback);
  // 以上述配置开始观察目标节点
  observer.observe(el, config);
  observerTemp.value = observer;
}

怎么用Vue3指令实现水印背景

从水印到取消水印(勾选到不勾选background-image):我们发现mutation.target属性中的oldValue值就是我们设置style。

怎么用Vue3指令实现水印背景

从取消水印到恢复水印(不勾选到勾选background-image):我们发现mutation.target属性中的oldValue值的background-image被注释掉了。

从上面两个转化中,我们就可以直接得出直接赋值当勾选到不勾选是监听到DOM修改的oldValue(真正的style),因为这时候获取到的才是真正style,反之就不是了,由于我们不勾选时的oldValue赋值给不勾选时的style,所以当我们不勾选时再转化为勾选时就是真正style,从而实现不管用户怎么操作都不能取消水印。

以上是怎么用Vue3指令实现水印背景的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:亿速云。如有侵权,请联系admin@php.cn删除
vue.js:定义其在网络开发中的作用vue.js:定义其在网络开发中的作用Apr 18, 2025 am 12:07 AM

Vue.js在Web开发中的角色是作为一个渐进式JavaScript框架,简化开发过程并提高效率。1)它通过响应式数据绑定和组件化开发,使开发者能专注于业务逻辑。2)Vue.js的工作原理依赖于响应式系统和虚拟DOM,优化性能。3)实际项目中,使用Vuex管理全局状态和优化数据响应性是常见实践。

了解vue.js:主要是前端框架了解vue.js:主要是前端框架Apr 17, 2025 am 12:20 AM

Vue.js是由尤雨溪在2014年发布的渐进式JavaScript框架,用于构建用户界面。它的核心优势包括:1.响应式数据绑定,数据变化自动更新视图;2.组件化开发,UI可拆分为独立、可复用的组件。

Netflix的前端:React(或VUE)的示例和应用Netflix的前端:React(或VUE)的示例和应用Apr 16, 2025 am 12:08 AM

Netflix使用React作为其前端框架。1)React的组件化开发模式和强大生态系统是Netflix选择它的主要原因。2)通过组件化,Netflix将复杂界面拆分成可管理的小块,如视频播放器、推荐列表和用户评论。3)React的虚拟DOM和组件生命周期优化了渲染效率和用户交互管理。

前端景观:Netflix如何处理其选择前端景观:Netflix如何处理其选择Apr 15, 2025 am 12:13 AM

Netflix在前端技术上的选择主要集中在性能优化、可扩展性和用户体验三个方面。1.性能优化:Netflix选择React作为主要框架,并开发了SpeedCurve和Boomerang等工具来监控和优化用户体验。2.可扩展性:他们采用微前端架构,将应用拆分为独立模块,提高开发效率和系统扩展性。3.用户体验:Netflix使用Material-UI组件库,通过A/B测试和用户反馈不断优化界面,确保一致性和美观性。

React与Vue:Netflix使用哪个框架?React与Vue:Netflix使用哪个框架?Apr 14, 2025 am 12:19 AM

NetflixusesAcustomFrameworkcalled“ Gibbon” BuiltonReact,notReactorVueDirectly.1)TeamExperience:selectBasedAsedonFamiliarity.2)ProjectComplexity:vueforsimplerprojects,vueforsimplerprojects,reactforforforecomplexones.3)cocatizationNeedsneeds:reactofficatizationneedneeds:reactofferizationneedneedneedneeds:reactoffersizatization needeffersefersmoreflexiblesimore.4)ecosyaka

框架的选择:是什么推动了Netflix的决定?框架的选择:是什么推动了Netflix的决定?Apr 13, 2025 am 12:05 AM

Netflix在框架选择上主要考虑性能、可扩展性、开发效率、生态系统、技术债务和维护成本。1.性能与可扩展性:选择Java和SpringBoot以高效处理海量数据和高并发请求。2.开发效率与生态系统:使用React提升前端开发效率,利用其丰富的生态系统。3.技术债务与维护成本:选择Node.js构建微服务,降低维护成本和技术债务。

反应,vue和Netflix前端的未来反应,vue和Netflix前端的未来Apr 12, 2025 am 12:12 AM

Netflix主要使用React作为前端框架,辅以Vue用于特定功能。1)React的组件化和虚拟DOM提升了Netflix应用的性能和开发效率。2)Vue在Netflix的内部工具和小型项目中应用,其灵活性和易用性是关键。

前端中的vue.js:现实世界的应用程序和示例前端中的vue.js:现实世界的应用程序和示例Apr 11, 2025 am 12:12 AM

Vue.js是一种渐进式JavaScript框架,适用于构建复杂的用户界面。1)其核心概念包括响应式数据、组件化和虚拟DOM。2)实际应用中,可以通过构建Todo应用和集成VueRouter来展示其功能。3)调试时,建议使用VueDevtools和console.log。4)性能优化可通过v-if/v-show、列表渲染优化和异步加载组件等实现。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前By尊渡假赌尊渡假赌尊渡假赌
威尔R.E.P.O.有交叉游戏吗?
1 个月前By尊渡假赌尊渡假赌尊渡假赌

热工具

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器