Maison > Article > interface Web > Comment utiliser les instructions Vue3 pour implémenter l'arrière-plan du filigrane
Je crois que nous avons tous rencontré le problème du filigrane de page. Pourquoi devons-nous ajouter un filigrane à la page ? Afin de protéger vos droits d'auteur et de propriété intellectuelle, l'ajout de filigranes aux images vise généralement à empêcher les pirates de les utiliser à des fins commerciales et de porter atteinte aux droits de l'auteur original. Alors, quelles méthodes pouvons-nous mettre en œuvre dans notre développement ? Généralement divisé en deux méthodes : l'implémentation front-end et l'implémentation back-end Cet article se concentre principalement sur l'apprentissage de la méthode d'implémentation front-end :
Méthode 1 : Wrap the. police directement avec les éléments de bloc, définissez dynamiquement le positionnement absolu, puis faites-la pivoter via l'attribut de transformation. Cependant, il y a un problème qui doit être pris en compte lorsque l'image est trop grande ou qu'il y a trop d'images, cela affectera sérieusement les performances, je n'entrerai donc pas dans les détails de cette méthode.
Méthode 2 : dessinez la police sur la toile, définissez le style, puis exportez-la sous forme d'image et utilisez l'image comme image d'arrière-plan du calque de filigrane .
Avant d'apprendre le calque de filigrane, je me pose d'abord deux questions :
Si le texte du filigrane est long , les filigranes peuvent-ils être adaptatifs ?
Les utilisateurs peuvent-ils être empêchés de modifier et de supprimer des filigranes ?
En fait, les deux questions ci-dessus sont les deux problèmes fondamentaux que nous devons prendre en compte lors de la création de filigranes de page. D'accord, sans plus tarder, explorons ensemble les questions. ????.
Définissez d'abord une commande. Nous devons clarifier deux points : le nom (v-water-mask) et la valeur de liaison (valeur de configuration, option). 🎜🎜# L'effet est comme le montre l'image ci-dessous :
De l'image ci-dessus, nous pouvons voir que le texte contient des chaînes de texte et d'heure, et le texte du filigrane est incliné dans une certaine mesure. L'angle est en fait pivoté d'un certain angle. Alors la question se pose, on peut se demander comment ceux-ci sont mis en place ? Tout d'abord, cela nécessite certaines configurations pour atteindre certaines valeurs fixes lors de l'utilisation des instructions. Ci-dessous, ces configurations sont encapsulées dans une classe. Pourquoi faisons-nous cela ? De cette façon, vous n'êtes pas obligé de définir une valeur par défaut à chaque fois que vous l'utilisez. Par exemple, lorsque vous référencez ces configurations en définissant une interface, vous devez définir une valeur par défaut à chaque fois :
<div v-water-mask:options="wmOption"></div> // 配置值 const wmOption = reactive<WMOptions>({ textArr: ['路灯下的光', `${dayjs().format('YYYY-MM-DD HH:mm')}`], deg: -35, });#🎜 🎜#Si vous faites attention, nous pouvons trouver que le texte affiché est un tableau, qui sert principalement à faciliter le saut de ligne. Intelligemment, nous pouvons demander : si l'un d'eux est plus long, comment rompre la ligne ? , ne vous inquiétez pas, ne vous inquiétez pas, comprenons d'abord comment l'instruction est définie : Définir l'instruction : définissez-la d'abord comme un type d'objet ObjectDirective, car l'instruction doit faire quelque chose au L'élément actuel fonctionne dans différents cycles de vie.
export class WMOptions { constructor(init?: WMOptions) { if (init) { Object.assign(this, init); } } textArr: Array<string> = ['test', '自定义水印']; // 需要展示的文字,多行就多个元素【必填】 font?: string = '16px "微软雅黑"'; // 字体样式 fillStyle?: string = 'rgba(170,170,170,0.4)'; // 描边样式 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 = '.75'; // 文字透明度 position?: 'fixed' | 'absolute' = 'fixed'; // 容器定位方式(值为absolute时,需要指定一个父元素非static定位) }waterMask méthode : réaliser la présentation des détails commerciaux du filigrane, le retour à la ligne adaptatif du texte et calculer les valeurs de largeur et de hauteur appropriées en fonction de la taille des éléments de la page .
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;Regardons cela ensuite Analysez les deux méthodes principales de filigrane une par une : waterMask et DisablePatchWaterMask. Implémentation de la fonction filigrane est implémentée via la méthode waterMask La méthode waterMask fait principalement quatre choses :
app.directive('water-mask', WaterMask);
Créer une balise canevas : Parce qu'elle est implémentée via canevas, nous ne présentons pas directement cette balise dans le modèle, nous devons donc créer une balise canevas via l'objet document :
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('水印文本必须放在数组中!'); } const c = createCanvas(); // 动态创建隐藏的canvas draw(c, defaultSettings); // 绘制文本 convertCanvasToImage(c, element); // 转化图像 };
Dessiner du texte : parcourez d'abord les informations de filigrane qui doivent être affichées, c'est-à-dire le tableau de texte textArr, parcourez le tableau pour déterminer si les éléments du tableau dépassent la largeur et la hauteur par défaut configurées de chaque filigrane , puis renvoie le tableau de segmentation de texte qui dépasse la longueur du texte en fonction des éléments de texte, et en même temps maximise le texte. La largeur est renvoyée, et enfin la largeur et la hauteur du canevas sont modifiées dynamiquement grâce aux résultats de découpe.
function createCanvas() { const c = document.createElement('canvas'); c.style.display = 'none'; document.body.appendChild(c); return c; }
À partir du code ci-dessus, nous pouvons voir que l'opération principale du dessin de texte est de couper le texte trop long et de modifier dynamiquement la largeur et la hauteur du canevas. Voyons comment ces deux opérations sont mises en œuvre ?
La méthode MeasureText() calcule la largeur de la chaîne en fonction de la police actuelle.
function draw(c: any, settings: WMOptions) { const ctx = c.getContext('2d'); // 切割超过最大宽度的文本并获取最大宽度 const textArr = settings.textArr || []; // 水印文本数组 let wordBreakTextArr: Array<any> = []; const maxWidthArr: Array<number> = []; // 遍历水印文本数组,判断每个元素的长度 textArr.forEach((text) => { const result = breakLinesForCanvas(ctx,text + '',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! + '', 10); c.height = parseInt(wSinDeg + hCosDeg + settings.marginBottom! + '', 10); // 宽高重置后,样式也需重置 ctx.font = settings.font; ctx.fillStyle = settings.fillStyle; ctx.textBaseline = 'hanging'; // 默认是alphabetic,需改基准线为贴着线的方式 // 移动并旋转画布 ctx.translate(0, wSinDeg); ctx.rotate(degToPI); // 绘制文本 wordBreakTextArr.forEach((text, index) => { ctx.fillText(text, 0, lineHeight * index); }); }
// 根据最大宽度切割文字 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, }; }La largeur graphique du canevas est donc hSinDeg + wCosDeg + settings.marginRight. La hauteur graphique du canevas est : wSinDeg + hCosDeg + settings.marginBottom.
Couper un texte super long :
Trouver le point de coupe : interroger la chaîne si elle est trop longue via la méthode de recherche binaire Où est la position :
Modifier dynamiquement la largeur et la hauteur de la toile : calculez la largeur et la hauteur une par une grâce à la valeur de l'angle de rotation, la valeur de largeur maximale et le théorème de Pythagore. Tout d'abord, nous devons convertir l'angle de rotation en valeur en radian (formule : π/180× angle, c'est-à-dire (Math.PI*settings.deg!) / 180) . Regardons d'abord la photo suivante :
.转化图像:通过对当前canvas配置转化为图形url,然后配置元素的style属性。
// 将绘制好的canvas转成图片 function convertCanvasToImage(canvas: any, el: HTMLElement) { // 判断是否为空渲染器 if (Util.isUndefinedOrNull(el)) { console.error('请绑定渲染容器'); } else { // 转化为图形数据的url const imgData = canvas.toDataURL('image/png'); 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 = 'url(' + imgData + ')'; divMask.style.backgroundPosition = defaultSettings.left + 'px ' + defaultSettings.top + 'px'; } }
我们都知道,如果用户需要修改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 'childList': if (mutation.removedNodes.length > 0) { // 删除节点,直接从删除的节点数组中添加回来 mutation.target.append(mutation.removedNodes[0]); } break; case 'attributes': // 为什么是这样处理,我们看一下下面两幅图 mutation.target.setAttribute('style', mutation.target.oldValue); break; default: break; } } }; // 创建一个观察器实例并传入回调函数 const observer = new MutationObserver(callback); // 以上述配置开始观察目标节点 observer.observe(el, config); observerTemp.value = observer; }
从水印到取消水印(勾选到不勾选background-image):我们发现mutation.target属性中的oldValue值就是我们设置style。
从取消水印到恢复水印(不勾选到勾选background-image):我们发现mutation.target属性中的oldValue值的background-image被注释掉了。
从上面两个转化中,我们就可以直接得出直接赋值当勾选到不勾选是监听到DOM修改的oldValue(真正的style),因为这时候获取到的才是真正style,反之就不是了,由于我们不勾选时的oldValue赋值给不勾选时的style,所以当我们不勾选时再转化为勾选时就是真正style,从而实现不管用户怎么操作都不能取消水印。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!