🐱 个人主页:不叫猫先生
🙋♂️ 作者简介:前端领域新星创作者、阿里云专家博主,专注于前端各领域技术,共同学习共同进步,一起加油呀!
💫系列专栏:vue3从入门到精通、TypeScript从入门到实践
📢 资料领取:前端进阶资料以及文中源码可以找我免费领取
🔥 前端学习交流:博主建立了一个前端交流群,汇集了各路大神,一起交流学习,期待你的加入!(文末有我wx或者私信)
目录
我们会看到很多页面带有水印,但是怎么实现呢?当然可以有多种实现方式,本文主要讲解在vue项目中基于DOM或者Cavans实现水印效果,当然还有其他的实现方式,比如在原图片的基础上加上水印生成新的图片,但是这需要后端处理。因为要在vue项目中使用,所以我使用自定义指令可以直接对挂载的dom实现水印效果。
本文实现水印的项目环境为:vue + vite + ts
前面专门有一篇讲解vue2.x与vue3.x中自定义指令详解
将水印的指令放到标签上,设置标签的宽高。水印可以放大div标签上,也可以是img标签上。注意:img才有onload方法,div标签么有。
<script setup lang="ts">
import { ref } from "vue";
</script>
<template>
<div class="index-content" >
<div class="watermaker" v-watermark ></div>
<!-- <img v-watermark style="width:400px;height:400px" src="../assets/vue.svg" alt=""> -->
</div>
</template>
<style scoped>
.watermaker {
width: 400px;
height: 400px;
}
.index-content{
width: 100%;
height: 100%;
}
</style>
directives文件在directives文件下创建waterMark.ts 文件,具体内容实现如下:
import waterImg from "@/assets/vue.svg"
const directives: any = {
mounted(el: HTMLElement) {
//如果el元素是img,则可以用el.onload将下面包裹
const { clientWidth, clientHeight, parentElement } = el;
console.log(parentElement, 'parentElement')
const waterMark: HTMLElement = document.createElement('div');
const waterBg: HTMLElement = document.createElement('div');
//设置waterMark的class和style
waterMark.className = `water-mark`;
waterMark.setAttribute('style', `
display: inline-block;
overflow: hidden;
position: relative;
width: ${clientWidth}px;
height: ${clientHeight}px;`);
// 创建waterBg的class和style
waterBg.className = `water-mark-bg`;// 方便自定义展示结果
waterBg.setAttribute('style', `
position: absolute;
pointer-events: none;`在这里插入代码片`
transform: rotate(45deg);
width: 100%;
height: 100%;
opacity: 0.2;
background-image: url(${waterImg});
background-repeat: repeat;
`);
// 水印元素waterBg放到waterMark元素中
waterMark.appendChild(waterBg);
//waterMark插入到el之前,即插入到绑定元素之前
parentElement?.insertBefore(waterMark, el);
// 绑定元素移入到包裹水印的盒子
waterMark.appendChild(el);
}
}
export default {
name: 'watermark',
directives
}
directives文件下创建 index.ts文件import type { App } from 'vue'
import watermark from './waterMark'
export default function installDirective(app: App) {
app.directive(watermark.name, watermark.directives);
}
main.ts中全局引入import { createApp } from 'vue'
import App from './App.vue'
import directives from './directives'
const app = createApp(App);
app.use(directives);
app.mount('#app');
MutationObserver对水印元素进行监听,删除时,我们再立即生成一个水印元素就可以了,具体方面在下面讲解。position:relative position:absolute ,实现这个水印元素覆盖到原始元素的上层,以实现水印的效果。通过将图片绘制在cavans中,然后通过cavans的toDataURL方法,将图片转为base64编码。
// 全局保存 canvas 和 div ,避免重复创建(单例模式)
const globalCanvas = null;
const globalWaterMark = null;
// 获取 toDataURL 的结果
const getDataUrl = (
// font = "16px normal",
// fillStyle = "rgba(180, 180, 180, 0.3)",
// textAlign,
// textBaseline,
// text = "请勿外传",
) => {
const rotate = -10;
const canvas = globalCanvas || document.createElement("canvas");
const ctx = canvas.getContext("2d"); // 获取画布上下文
ctx.rotate((rotate * Math.PI) / 180);
ctx.font = "16px normal";
ctx.fillStyle = "rgba(180, 180, 180, 0.3)";
ctx.textAlign = "left";
ctx.textBaseline = "middle";
ctx.fillText('请勿外传', canvas.width / 3, canvas.height / 2);
return canvas.toDataURL("image/png");
};
使用MutationObserver监听dom变化,MutationObserver详细用法之前已经讲过了,详细可见作为前端你还不懂MutationObserver?那Out了
具体监听逻辑如下:
// watermark 样式
let style = `
display: block;
overflow: hidden;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-repeat: repeat;
pointer-events: none;`;
//设置水印
const setWaterMark = (el: HTMLElement, binding: any) => {
const { parentElement } = el;
// 获取对应的 canvas 画布相关的 base64 url
const url = getDataUrl(binding);
// 创建 waterMark 父元素
const waterMark = globalWaterMark || document.createElement("div");
waterMark.className = `water-mark`; // 方便自定义展示结果
style = `${style}background-image: url(${url});`;
waterMark.setAttribute("style", style);
// 将对应图片的父容器作为定位元素
parentElement.setAttribute("style", "position: relative;");
// 将图片元素移动到 waterMark 中
parentElement.appendChild(waterMark);
};
// 监听 DOM 变化
const createObserver = (el: HTMLElement, binding: any) => {
console.log(el, 'el')
console.log(style, 'style')
// console.log(el.parentElement.querySelector('.water-mark'),'el.parentElement')
const waterMarkEl = el.parentElement.querySelector(".water-mark");
const observer = new MutationObserver((mutationsList) => {
console.log(mutationsList, 'mutationsList')
if (mutationsList.length) {
const { removedNodes, type, target } = mutationsList[0];
const currStyle = waterMarkEl.getAttribute("style");
// console.log(currStyle, 'currStyle')
// 证明被删除了
// (1)直接删除dom
// 1.先获取设置水印的dom
// 2.监听到被删除元素的dom
// 如果他两相等的话就停止观察,初始化(设置水印+启动监控)
// (2) 删除style中的属性
// 1 判断删除的是否是标签的属性 (type === "attributes")
// 2.判断删除的标签属性是否是在设置水印的标签上
// 3.判断修改过的style和之前的style对比,不等的话,重新赋值
if (removedNodes[0] === waterMarkEl) {
console.log(removedNodes[0])
// 停止观察。调用该方法后,DOM 再发生变动,也不会触发观察器。
observer.disconnect();
//初始化(设置水印,启动监控)
init(el, binding);
} else if (
type === "attributes" &&
target === waterMarkEl &&
currStyle !== style
) {
console.log(currStyle, 'currStyle')
console.log(style, 'style')
waterMarkEl.setAttribute("style", style);
}
}
});
observer.observe(el.parentElement, {
childList: true,
attributes: true,
subtree: true,
});
};
// 初始化
const init = (el: HTMLElement, binding: any = {}) => {
// 设置水印
setWaterMark(el, binding.value);
// 启动监控
createObserver(el, binding.value);
};
const directives: any = {
mounted(el: HTMLElement, binding: any) {
//注意img有onload的方法,如果自定义指令注册在html标签的话,只需要init(el, binding.value)
el.onload = init.bind(null, el, binding.value);
},
};
删除水印标签依然还在,除非删除水印注册的标签才能删除水印,但是这样做毫无意义,因为这样做内容也会全部删除掉。

toDataURL(type, encoderOptions),接收两个参数:
image/png、image/jpeg、image/webp等等,默认为image/png格式我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复
require'mechanize'agent=Mechanize.newlogin=agent.get('http://www.schoolnet.ch/DE/HomeDE.htm')agent.clicklogin.link_withtext:/Login/然后我得到Mechanize::UnsupportedSchemeError。 最佳答案 Mechanize不支持javascript但您可以将搜索字段添加到表单并为其分配搜索词并使用mechanize提交表单form=page.forms.firstform.add_fie
在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定