水印项目的实现以及两种实现方案的选优入门基础教程_项目小白常识

水印项目我们提出了两种解决方案一、用shadow dom实现1、基本思路通过 attachShadow 这个方法生成一个shadow root 即shadow的根节点,然后在这个根节点下面通过循环语句添加水印,利用position为absolute进行排版,使其铺满容器 show me the code:(function (root, factory) {

水印项目的实现以及两种实现方案的选优入门基础教程

水印项目我们提出了两种解决方案

水印项目的实现以及两种实现方案的选优入门基础教程_项目小白常识

一、用shadow dom实现

1、基本思路

通过 attachShadow 这个方法生成一个shadow root 即shadow的根节点,然后在这个根节点下面通过循环语句添加水印,利用position为absolute进行排版,使其铺满容器

 show me the code:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        /*AMD. Register as an anonymous module.
        *define([], factory); */
        define([], factory());
    } else if (typeof module === 'object' && module.exports) {
        /*Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.*/
        module.exports = factory();

    } else {
        /*Browser globals (root is window)*/
        root['watermark'] = factory();
    }
}(this, function () {

    /*Just return a value to define the module export.*/
    let watermark = {};

    /*加载水印*/
    let loadMark = function () {
        let defaultSettings = {
            watermark_id: 'wm_div_id',          //水印总体的id
            watermark_prefix: 'mask_div_id',    //小水印的id前缀
            watermark_txt: "测试水印",             //水印的内容
            watermark_x: 20,                     //水印起始位置x轴坐标
            watermark_y: 40,                     //水印起始位置Y轴坐标
            watermark_rows: 0,                   //水印行数
            watermark_cols: 0,                   //水印列数
            watermark_x_space: 100,              //水印x轴间隔
            watermark_y_space: 50,               //水印y轴间隔
            watermark_font: '微软雅黑',           //水印字体
            watermark_color: 'black',            //水印字体颜色
            watermark_fontsize: '18px',          //水印字体大小
            watermark_alpha: 0.15,               //水印透明度,要求设置在大于等于0.005
            watermark_width: 100,                //水印宽度
            watermark_height: 100,               //水印长度
            watermark_angle: 15,                 //水印倾斜度数
            watermark_parent_selector: null,  //水印插件挂载的父元素选取器,不输入则默认挂在body上
            need_adapt_screen: false,     // 是否根据屏幕的分辨率等比变化每个水印的宽度和字体大小
            watermark_width_proportion: 15, // 每个水印宽度自适应屏幕变化的等比放大或缩小的值
            watermark_fontsize_proportion: 95, // 每个水印字体大小自适应屏幕变化的等比放大或缩小的值
        };
        let watermark_parent_node = null  //水印插件挂载的父元素element,不输入则默认挂在body上

        let setting = arguments[0] || {};
        /*采用配置项替换默认值,作用类似jquery.extend*/
        if (arguments.length === 1 && typeof arguments[0] === "object") {
            for (let key in setting) {
                if (setting[key] && defaultSettings[key] && setting[key] === defaultSettings[key]) continue;
                /*veronic: resolution of watermark_angle=0 not in force*/
                else if (setting[key] || setting[key] === 0) defaultSettings[key] = setting[key];
            }
        }

        /* 设置水印的容器 */
        if (defaultSettings.watermark_parent_selector) {
            watermark_parent_node = document.querySelector(defaultSettings.watermark_parent_selector)
        } else {
            watermark_parent_node = document.body
        }

        /*如果元素存在则移除*/
        let watermark_element = document.getElementById(defaultSettings.watermark_id);
        if (watermark_element) {
            let _parentElement = watermark_element.parentNode;
            if (_parentElement) {
                _parentElement.removeChild(watermark_element);
            }
        }
        
        /*获取水印的起始位置*/
        let page_offsetTop = 0;
        let page_offsetLeft = 0;
        page_offsetTop = watermark_parent_node.offsetTop || 0;
        page_offsetLeft = watermark_parent_node.offsetLeft || 0;
        page_width = watermark_parent_node.offsetWidth - defaultSettings.watermark_width / 2 || 0;
        page_height = (Math.max(watermark_parent_node.offsetHeight, watermark_parent_node.scrollHeight) - defaultSettings.watermark_height / 2) || 0;
        defaultSettings.watermark_x = defaultSettings.watermark_x + page_offsetLeft;
        defaultSettings.watermark_y = defaultSettings.watermark_y + page_offsetTop;

        /*创建水印外壳div*/
        let otdiv = document.getElementById(defaultSettings.watermark_id);
        let shadowRoot = null;

        if (!otdiv) {
            otdiv = document.createElement('div');

            /*创建shadow dom*/
            otdiv.id = defaultSettings.watermark_id;
            otdiv.style.pointerEvents = "none";

            /*判断浏览器是否支持attachShadow方法*/
            if (typeof otdiv.attachShadow === 'function') {
                shadowRoot = otdiv.attachShadow({mode: 'open'});
            } else if (typeof otdiv.createShadowRoot === 'function') {
                shadowRoot = otdiv.createShadowRoot();
            } else {
                shadowRoot = otdiv;
            }

            watermark_parent_node.appendChild(otdiv)

        } else if (otdiv.shadowRoot) {
            shadowRoot = otdiv.shadowRoot;
        }

        // shadow内加个容器
        let shadowOutDiv = document.createElement('div')
        shadowOutDiv.id = 'shadowContainer'
        shadowRoot.appendChild(shadowOutDiv)


        /*如果将水印列数设置为0,或水印列数设置过大,超过页面最大宽度,则重新计算水印列数和水印x轴间隔*/
        if (defaultSettings.watermark_cols == 0 || (parseInt(defaultSettings.watermark_x + defaultSettings.watermark_width * defaultSettings.watermark_cols + defaultSettings.watermark_x_space * (defaultSettings.watermark_cols - 1)) > page_width)) {
            defaultSettings.watermark_cols = parseInt((page_width - defaultSettings.watermark_x + page_offsetLeft) / (defaultSettings.watermark_width + defaultSettings.watermark_x_space));
            defaultSettings.watermark_x_space = parseInt((page_width - defaultSettings.watermark_x + page_offsetLeft - defaultSettings.watermark_width * defaultSettings.watermark_cols) / (defaultSettings.watermark_cols - 1));
        }
        /*如果将水印行数设置为0,或水印行数设置过大,超过页面最大长度,则重新计算水印行数和水印y轴间隔*/
        if (defaultSettings.watermark_rows == 0 || (parseInt(defaultSettings.watermark_y + defaultSettings.watermark_height * defaultSettings.watermark_rows + defaultSettings.watermark_y_space * (defaultSettings.watermark_rows - 1)) > page_height)) {
            defaultSettings.watermark_rows = parseInt((page_height - defaultSettings.watermark_y + page_offsetTop) / (defaultSettings.watermark_height + defaultSettings.watermark_y_space));
            defaultSettings.watermark_y_space = parseInt(((page_height - defaultSettings.watermark_y + page_offsetTop) - defaultSettings.watermark_height * defaultSettings.watermark_rows) / (defaultSettings.watermark_rows - 1));
        }

        let x;
        let y;
        for (let i = 0; i < defaultSettings.watermark_rows; i++) {
            y = defaultSettings.watermark_y + (defaultSettings.watermark_y_space + defaultSettings.watermark_height) * i;
            for (let j = 0; j < defaultSettings.watermark_cols; j++) {
                x = defaultSettings.watermark_x + (defaultSettings.watermark_width + defaultSettings.watermark_x_space) * j;

                let mask_div = document.createElement('div');
                let oText = document.createTextNode(defaultSettings.watermark_txt);
                mask_div.appendChild(oText);
                /*设置水印相关属性start*/
                mask_div.id = defaultSettings.watermark_prefix + i + j;
                /*设置水印div倾斜显示*/
                mask_div.style.webkitTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
                mask_div.style.MozTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
                mask_div.style.msTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
                mask_div.style.OTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
                mask_div.style.transform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
                mask_div.style.visibility = "";
                mask_div.style.position = "absolute";
                /*选不中*/
                mask_div.style.left = x + 'px';
                mask_div.style.top = y + 'px';
                mask_div.style.overflow = "hidden";
                mask_div.style.zIndex = "9999999";
                mask_div.style.opacity = defaultSettings.watermark_alpha;
                mask_div.style.fontSize = defaultSettings.watermark_fontsize;
                mask_div.style.fontFamily = defaultSettings.watermark_font;
                mask_div.style.color = defaultSettings.watermark_color;
                mask_div.style.textAlign = "center";
                mask_div.style.width = defaultSettings.watermark_width + 'px';
                mask_div.style.height = defaultSettings.watermark_height + 'px';
                mask_div.style.display = "block";
                mask_div.style['-ms-user-select'] = "none";
                /*设置水印相关属性end*/
                shadowOutDiv.appendChild(mask_div);

            }
        }
    };

    /*初始化水印,添加load和resize事件*/
    watermark.init = function (settings) {
        window.addEventListener('load', function () {
            loadMark(settings);
        });
        window.addEventListener('resize', function () {
            loadMark(settings);
        });
        window.addEventListener('DOMContentLoaded', function () {
            loadMark(settings);
        });

    };

    /*手动加载水印*/
    watermark.load = function (settings) {
        loadMark(settings);
        observerDomReloadMark(settings)
    };

    return watermark;
}))

2、优化

但是毕竟水印是有关安全方面的,如果别人打开开发者模式很轻松的就水印的内容更改甚至完全删除,所以不能就这么完事了。我们需要监听DOM的变化,判断如果用户通过开发者工具手动修改了水印的dom,我们就重新渲染水印,实现水印不能手动进行了修改。

思路:通过mutationObsever监听DOM的变化,判断是否是水印部分的dom 变化,如果是则重新渲染

show me the code:

/* 监听DOM的变化,防止手动删除 */
    let observerDomReloadMark = (settings) => {
        // Select the node that will be observed for mutations
        let observer_node = document.querySelector('#shadowContainer')
        // Options for the observer (which mutations to observe)
        let config = {
            attributes: true,
            childList: true,
            subtree: true
        };
        // Callback function to execute when mutations are observed
        const mutationCallback = (mutationsList) => {
            // for (let mutation of mutationsList) {
            //     let type = mutation.type;
            //     switch (type) {
            //         case "childList":
            //             console.log("A child node has been added or removed.");
            //             break;
            //         case "attributes":
            //             console.log(`The ${mutation.attributeName} attribute was modified.`);
            //             break;
            //         case "subtree":
            //             console.log(`The subtree was modified.`);
            //             break;
            //         default:
            //             break;
            //     }
            // }
            // loadMark(settings)
            console.log(2222)
        };

        // Create an observer instance linked to the callback function
        let observer = new MutationObserver(mutationCallback);

        // Start observing the target node for configured mutations
        observer.observe(observer_node, config);

        // Later, you can stop observing
        // observer.disconnect();
    }

这个方法看起来完美,然而现实很残酷,当我们再浏览器中看效果的时候发现mutationObsever不能监听shadow的变化,what? 查资料后发现shadow是独立于dom树的,相当于被隔离起来的dom。既然如此那就放弃装逼吧,还是老老实实用正常的dom实现吧,有多少个水印就生成多个水印的dom,这必要会导致性能问题。好吧,我们还是再另寻他法。

二、canvas绘图

1、基本思路

通过canvas把单个水印绘制出来,用toDataURL方法生成图片的base64编码,放在水印容器的background-image:url()里面,背景图片会自动铺满整个容器。思想比第一种方案简单很多。

show me the code:

      var canvas = document.createElement('canvas')
      canvas.id = 'canvas'
      canvas.width = defaultSettings.watermark_width // 单个水印的宽度
      canvas.height = defaultSettings.watermark_height // 单个水印的高度
      var ctx = canvas.getContext('2d')
      ctx.font = 'normal 12px Microsoft Yahei' // 设置样式
      ctx.fillStyle = 'rgba(112, 113, 114, 0.2)' // 水印字体颜色
      ctx.translate(canvas.width/2,canvas.height/2)
      ctx.rotate((defaultSettings.watermark_angle * Math.PI) / 180) // 水印偏转角度
      ctx.translate(-canvas.width/2,-canvas.height/2)
      ctx.fillText(defaultSettings.watermark_txt, 30, 20)
      var src = canvas.toDataURL('image/png')

      /* 设置水印的容器 */
      var watermark_parent_node = null
      if (defaultSettings.watermark_parent_selector) {
          watermark_parent_node = document.querySelector(defaultSettings.watermark_parent_selector)
      } else {
          watermark_parent_node = document.body
      }
     watermark_parent_node.style.pointerEvents = 'none'
      // 判断容器是否已经设置了backgroundImage属性值
      if (!watermark_parent_node.style.backgroundImage) {
          watermark_parent_node.style.backgroundImage = 'URL(' + src + ')'
      } else {
          watermark_parent_node.style.backgroundImage = watermark_parent_node.style.backgroundImage + ', URL(' + src + ')'
      }

2、优化

优化思路和第一种方案一样,只不过这种方案的好处就是可以直接监听到变化。

三、两种方案的对比

方案优点缺点
shadow DOM 1、低耦合,shadow dom与原先的DOM树隔离,不会影响到系统本有的功能。

1、shadow DOM不能够被监听到

2、水印文案及DOM被篡改的成本较低

3、实现逻辑比较复杂

canvas绘图

1、实现逻辑比较清晰

2、水印数据生成图片,用户想篡改比较难

3、水印被篡改能够被监听到

 

1、水印图片是被放在background-image里面的,如果原先在class里面设置了background-image属性的话会被覆盖掉

原文:https://my.oschina.net/hrw/blog/3064362

海计划公众号
(0)
上一篇 2020/03/29 01:46
下一篇 2020/03/29 01:46

您可能感兴趣的内容

  • 你和阿里员工的技术水平到底差几个等级零基础入门_技术入门基础教程

    根据近年数据,中国现有程序员500万左右,其中P1、P2数量占据了近100万,P8及以下程序员约有490万,P9及以上仅有10万。80后是企业的技术支柱,90后已开始逐步成为企业的中坚力量。BAT的大佬横行,业内的散客也不容小觑。90后有人在P4彻夜敲代码,也有人正迈入P8。目前并没有对程序员等级进行明确的划分,很多时候是参照BAT的程序员等级进行判定。P1

    2020/03/30
  • mongoose小白攻略_在node.js异步环境下对mongodb进行便捷操作的对象模型工具

    mongoose小白攻略 官方网址:http://mongoosejs.com GitHub:https://github.com/Automattic/mongoose 简介描述…

    2020/03/06
  • 为什么要用webpack?使用教程_webpack指南教程

    现今的很多网页其实可以看做是功能丰富的应用,它们拥有着复杂的JavaScript代码和一大堆依赖包。模块化,让我们可以把复杂的程序细化为小的文件; 类似于TypeScript这种在JavaScript基础上拓展的开发语言:使我们能够实现目前版本的JavaScript不能直接使用的特性,并且之后还能转换为JavaScript文件使浏览器可以识别;Scss,le

    Web前端 2020/03/26
  • 了解HTML及运行原理小白攻略_html基础入门

    了解HTMLHTML(HyperText Marked Language)即超文本标记语言,是一种用来制作超文本文档的简单标记语言。我们在浏览网页时看到的一些丰富的影像、文字、图片等内容都是通过HTML表现出来的。用HTML编写的超文本文档称为HTML文档,它能独立于各种操作系统平台,一直被用作WWW(万维网)的信息表示语言。对于网站软件开发人员来讲,如果不

    2020/03/24
  • js栈和堆的区别菜鸟教程_区别入门指南

    一、 堆(heap)和栈(stack)栈(stack)会自动分配内存空间,会自动释放。堆(heap)动态分配的内存,大小不定也不会自动释放。二、 基本类型和引用类型基本类型:简单的数据段,存放在栈内存中,占据固定大小的空间。引用类型:指那些可能由多个值构成的对象,保存在堆内存中,包含引用类型的变量实际上保存的不是变量本身,二十指向该对象的指针。基本数据类

    2020/03/22
  • 10个新手常见的关于区块链和加密货币误解小白指南_区块链小白基础

    在本文中,我们将探讨有关加密货币和区块链技术的常见误解。误解1:区块链需要加密货币才能发挥作用。大多数加密货币依赖区块链技术,但是,在没有加密货币的情况下,区块链也可以发挥作用。加密货币是区块链技术的一种应用。(头等仓注:中国鼓励区块链技术与产业创新结合,但不等于鼓励虚拟货币无序发展,而是要实现区块链技术无币化。)误解2:记录在区块链上的交易是不可篡改的。区

    2020/03/24
  • Vue自定义指令:实现文字溢出显示、鼠标移入浮层展示全部入门攻略_指令基础知识入门

    首先要知道当前元素的宽
    将文字放到一个容器中,将容器的样式(主要是有关字体的样式)都设置为当前元素的样式,然后获取容器的宽,也就是文字的宽
    如果文字的宽度超过了当前元素的宽度,则给溢出隐藏的css样式 overflow :hidden;text-overflow: ellipsis;white-space: normal定义鼠标移入展示浮层,浮层中显示全部内

    Web前端 2020/03/20
  • 初级程序员如何提升自己?入门攻略_程序员零基础入门

    1、牢记基础,领悟原理无论各行各业,基础是最关键的。好比你是个大作家,结果老提笔忘字,提笔忘词,那么你又如何写出精美的文章呢。做程序开发也是如此,既要清楚基本技术,也要深刻领悟其原理,这样在以后的开发过程中才能运用自如。2、精于数据库,了解操作系统这一块,往往是新手忽视的,想要成为高手,必须精通这些。当然,对于刚刚入门的新手,可以一步一步的领悟。只有了解操作

    2020/03/23
  • Web前端知识体系精简入门基础教程_web菜鸟指南

    Web前端技术由 html、css 和 javascript 三大部分构成,是一个庞大而复杂的技术体系,其复杂程度不低于任何一门后端语言。而我们在学习它的时候往往是先从某一个点切入,然后不断地接触和学习新的知识点,因此对于初学者很难理清楚整个体系的脉络结构。本文将对Web前端知识体系进行简单的梳理,对应的每个知识点点到为止,不作详细介绍。目的是帮助大家审查自

    2020/04/05
  • 小程序如何实现rem?入门知识_rem基础知识

    最近在学习小程序,要把html的代码转换成小程序界面,其中就遇到了rem的转换问题,但小程序不太兼容rem,不是不能用rem,而是没办法设置根元素的font-size,因为rem是相对于根元素的font-size,而小程序的根元素font-size一直是16px。既然我设置了page{ font-size:1px }也不起作用,那要怎么实现rem呢这里我用c

    2020/03/23
  • Polar小白常识_一个用于PDF和Web内容的个人知识库,支持增量阅读和文档注释

    Polar小白常识 官方网址:https://github.com/burtonator/polar-bookshelf GitHub:https://getpolarized.i…

    2020/03/08
  • Css边框阴影:box-shadow属性入门指南_属性使用教程

    天在写一个点亮灯泡的小项目的时候,用到了box-shadow属性。感觉这个属性挺有意思的,索性专门整理一下。一. box-shadow的定义和语法定义:box-shadow是css3新增的一个属性。在W3School里,定义box-shadow是向框添加一个或者多个阴影的属性。 语法:box-shadow: h-shadow v-shadow blur sp

    2020/03/26
  • placeholder.js小白攻略一款轻量级的可在浏览器端生成占位图片的js插件

    placeholder.js基础入门 官方网址:http://placeholder.cn/ GitHub:https://github.com/hustcc/placeholde…

    2020/03/05
  • js函数式编程与代码执行效率使用帮助_代码小白攻略

    偶尔我也有意识的读一些关于函数式编程的文章, 虽然在工作中实践的机会不多, 但我十分喜欢函数式编程的风格. 在现代浏览器中, 使用函数式编程实用且高大上.函数式编程对应的是命令式编程, 函数式编程的核心当然是对函数的运用. 而高阶函数(Higher-order)是实现函数式编程的基本要素高阶函数可以将其他函数作为参数或者返回结果。所以JS天生就支持函数式编程

    2020/04/03
  • css transparent属性小白教程css 透明颜色transparent的使用_透明使用帮助

    在css中 transparent到底是什么意思呢? transparent它代表着全透明黑色,即一个类似rgba(0,0,0,0)这样的值。例如在css属性中定义:background:transparent,意思就代表背景透明。实际上background默认的颜色就是透明的属性,所以写和不写都是一样的。transparent一般使用场景:如果一个元素覆盖

    2020/04/05
  • web性能优化的15条实用技巧小白教程_技巧菜鸟教程网

    javascript在浏览器中运行的性能,可以认为是开发者所面临的最严重的可用性问题。这个问题因为javascript的阻塞性而变得复杂,事实上,多数浏览器使用单一进程来处理用户界面和js脚本执行,所以同一时刻只能做一件事。js执行过程耗时越久,浏览器等待响应的时间越长。一.提高加载性能1.IE8,FF,3.5,Safari 4和Chrome都允许并行下载j

    2020/03/24