Rumah >hujung hadapan web >View.js >Cara menggunakan arahan Vue3 untuk melaksanakan latar belakang tera air
Saya percaya kita semua pernah mengalami perniagaan tera air halaman. Mengapakah kita perlu menambah tera air pada halaman? Untuk melindungi hak cipta dan hak harta intelek anda sendiri, menambahkan tera air pada gambar secara amnya untuk menghalang lanun daripada menggunakannya untuk tujuan komersial dan merosakkan hak pengarang asal. Jadi apakah kaedah yang boleh dicapai dalam pembangunan kita? Secara amnya terbahagi kepada dua kaedah: pelaksanaan bahagian hadapan dan pelaksanaan bahagian belakang Artikel ini tertumpu terutamanya pada pembelajaran kaedah pelaksanaan bahagian hadapan:
Kaedah 1: Balut fon terus dengan blok. elemen dan tetapkan kedudukan mutlak secara dinamik Kemudian putarkannya melalui atribut transformasi. Walau bagaimanapun, terdapat masalah yang perlu dipertimbangkan apabila gambar terlalu besar atau terlalu banyak gambar, ia akan menjejaskan prestasi secara serius, jadi saya tidak akan menjelaskan secara terperinci tentang kaedah ini.
Kaedah 2: Lukis fon pada kanvas, tetapkan gaya, dan akhir sekali eksportnya sebagai gambar, dan gunakan gambar sebagai imej latar belakang lapisan tera air.
Sebelum mempelajari lapisan tera air, saya mula-mula menimbulkan dua soalan:
Jika teks tera air itu panjang, adakah tera air itu boleh disesuaikan?
Bolehkah pengguna disekat daripada mengubah suai dan memadam tera air?
Sebenarnya, dua isu di atas adalah dua isu teras yang perlu kita pertimbangkan semasa membuat watermark halaman.
Pertama-tama tentukan perintah Kita perlu menjelaskan dua perkara: penamaan (v-water-mask) dan nilai mengikat (nilai konfigurasi, pilihan). kesan adalah seperti yang ditunjukkan di bawah Paparan:
Daripada gambar di atas, kita dapat melihat bahawa teks mempunyai rentetan teks dan masa, dan teks tera air dicondongkan pada sudut tertentu, yang sebenarnya diputar pada sudut tertentu. Jadi persoalannya datang, kita mungkin bertanya bagaimana ini disediakan? Pertama sekali, ini memerlukan beberapa konfigurasi untuk mencapai beberapa nilai tetap apabila menggunakan arahan Di bawah, konfigurasi ini dirangkumkan ke dalam kelas Mengapa kita melakukan ini? Dengan cara ini, anda tidak perlu menetapkan nilai lalai setiap kali anda menggunakannya Sebagai contoh, apabila anda merujuk konfigurasi ini dengan mentakrifkan antara muka, anda perlu menetapkan nilai lalai setiap kali:
<div v-water-mask:options="wmOption"></div> // 配置值 const wmOption = reactive<WMOptions>({ textArr: ['路灯下的光', `${dayjs().format('YYYY-MM-DD HH:mm')}`], deg: -35, });
Jika anda berhati-hati, kami mungkin mendapati bahawa lokasi paparan Teks ialah tatasusunan, yang terutamanya untuk kemudahan pemutusan baris Secara bijak, kami mungkin bertanya: Jika salah satu daripadanya lebih panjang, bagaimana untuk memutuskan baris? , jangan risau, jangan risau, mari kita fahami dahulu bagaimana arahan itu ditakrifkan:
Tentukan arahan: Mula-mula takrifkannya sebagai jenis objek ObjectDirective, kerana arahan itu adalah untuk melaksanakan beberapa operasi pada arus elemen dalam kitaran hidup yang berbeza.
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定位) }
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;
Seterusnya, mari analisa dua tera air satu demi satu Dua kaedah teras: waterMask dan disablePatchWaterMask.
Laksanakan fungsi tera air
app.directive('water-mask', WaterMask);
Dapatkan nilai lalai konfigurasi: apabila pembangun lulus. parameter Anda tidak semestinya perlu memasukkan semua konfigurasi Sebenarnya, ikuti beberapa nilai lalai Dengan menyalin nilai yang diikat oleh arahan dan menggabungkannya bersama-sama, anda boleh mengemas kini konfigurasi lalai.
Buat teg kanvas : Kerana ia dilaksanakan melalui kanvas, kami tidak membentangkan label ini secara langsung dalam templat, jadi kami perlu mencipta label kanvas melalui objek dokumen:
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); // 转化图像 };
Teks lukisan : Mula-mula melintasi maklumat tera air masuk yang perlu dipaparkan, dan juga Ia ialah tatasusunan teks textArr Ia merentasi tatasusunan untuk menentukan sama ada elemen tatasusunan melebihi lebar dan ketinggian lalai setiap tera air yang dikonfigurasikan yang melebihi panjang teks mengikut elemen teks Pada masa yang sama, ia mengembalikan lebar maksimum teks Akhirnya, kanvas diubah suai secara dinamik melalui hasil pemotongan.
function createCanvas() { const c = document.createElement('canvas'); c.style.display = 'none'; document.body.appendChild(c); return c; }
Daripada kod di atas, kita dapat melihat bahawa operasi teras teks lukisan adalah untuk memotong teks yang terlalu panjang dan mengubah suai lebar dan ketinggian kanvas secara dinamik. Mari kita lihat bagaimana kedua-dua operasi ini dilaksanakan?
Kaedah measureText() mengira lebar rentetan berdasarkan fon semasa.
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, }; }
Jadi lebar grafik kanvas ialah hSinDeg + wCosDeg + settings.marginRight. Ketinggian grafik kanvas ialah: wSinDeg + hCosDeg + settings.marginBottom.
转化图像:通过对当前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,从而实现不管用户怎么操作都不能取消水印。
Atas ialah kandungan terperinci Cara menggunakan arahan Vue3 untuk melaksanakan latar belakang tera air. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!