The implementation of watermark project and the optimization of two implementation schemes

For the watermark project, we propose two solutions

 

1, Using shadow dom to realize

 

1. Basic ideas

Generate a shadow root, i.e. the root node of the shadow, through the method of "attachShadow", and then add watermark under the root node through circular statements, and use position to typeset absolute to make it full of containers

 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 = {};

    /*Load watermark*/
    let loadMark = function () {
        let defaultSettings = {
            watermark_id: 'wm_div_id',          //id of the watermark population
            watermark_prefix: 'mask_div_id',    //id prefix of small watermark
            watermark_txt: "Test watermark",             //Content of watermark
            watermark_x: 20,                     //x-axis coordinate of watermark starting position
            watermark_y: 40,                     //Y-axis coordinate of watermark starting position
            watermark_rows: 0,                   //Number of watermark lines
            watermark_cols: 0,                   //Number of watermark columns
            watermark_x_space: 100,              //Watermark x-axis interval
            watermark_y_space: 50,               //Watermark y-axis spacing
            watermark_font: 'Microsoft YaHei ',           //Watermark font
            watermark_color: 'black',            //Watermark font color
            watermark_fontsize: '18px',          //Watermark font size
            watermark_alpha: 0.15,               //Watermark transparency is required to be set at or above 0.005
            watermark_width: 100,                //Watermark width
            watermark_height: 100,               //Watermark length
            watermark_angle: 15,                 //Watermark tilt degree
            watermark_parent_selector: null,  //The parent element picker mounted by the watermark plug-in. If it is not input, it will be hung on the body by default
            need_adapt_screen: false,     // Does the width and font size of each watermark change proportionally according to the resolution of the screen
            watermark_width_proportion: 15, // Each watermark width adapts to the value of equal magnification or reduction of screen changes
            watermark_fontsize_proportion: 95, // Each watermark font size adapts to the proportionally enlarged or reduced value of the screen change
        };
        let watermark_parent_node = null  //The parent element of the watermark plug-in. If it is not entered, it will be hung on the body by default

        let setting = arguments[0] || {};
        /*Replace the default value with configuration item, which is similar to 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];
            }
        }

        /* Container for setting watermark */
        if (defaultSettings.watermark_parent_selector) {
            watermark_parent_node = document.querySelector(defaultSettings.watermark_parent_selector)
        } else {
            watermark_parent_node = document.body
        }

        /*Remove if element exists*/
        let watermark_element = document.getElementById(defaultSettings.watermark_id);
        if (watermark_element) {
            let _parentElement = watermark_element.parentNode;
            if (_parentElement) {
                _parentElement.removeChild(watermark_element);
            }
        }
        
        /*Get the starting position of the watermark*/
        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;

        /*Create watermark shell div*/
        let otdiv = document.getElementById(defaultSettings.watermark_id);
        let shadowRoot = null;

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

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

            /*Determine whether the browser supports the attachShadow method*/
            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;
        }

        // Add a container in shadow
        let shadowOutDiv = document.createElement('div')
        shadowOutDiv.id = 'shadowContainer'
        shadowRoot.appendChild(shadowOutDiv)


        /*If the number of watermark columns exceeds 0, the maximum number of watermark columns will be recalculated. If the number of watermark columns exceeds 0, the maximum number of watermark columns will be recalculated*/
        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));
        }
        /*If the number of watermark lines is set to 0, or the number of watermark lines is set too large to exceed the maximum length of the page, the number of watermark lines and the watermark y-axis interval will be recalculated*/
        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);
                /*Set watermark related property start*/
                mask_div.id = defaultSettings.watermark_prefix + i + j;
                /*Set watermark div tilt display*/
                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";
                /*Fail to choose*/
                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";
                /*Set watermark related properties*/
                shadowOutDiv.appendChild(mask_div);

            }
        }
    };

    /*Initialize the watermark and add load and resize events*/
    watermark.init = function (settings) {
        window.addEventListener('load', function () {
            loadMark(settings);
        });
        window.addEventListener('resize', function () {
            loadMark(settings);
        });
        window.addEventListener('DOMContentLoaded', function () {
            loadMark(settings);
        });

    };

    /*Manually load watermark*/
    watermark.load = function (settings) {
        loadMark(settings);
        observerDomReloadMark(settings)
    };

    return watermark;
}))

 

 

2. Optimize

But after all, the watermark is related to security. If others open the developer mode, it is easy to change or even delete the content of the watermark completely, so it can't be done like this. We need to monitor the changes of DOM and judge that if the user manually modifies the dom of the watermark through the developer tool, we will re render the watermark to realize that the watermark cannot be modified manually.

 

Idea: monitor the change of dom through mutationobserver to determine whether it is the dom change of watermark part. If so, re render it

show me the code:

/* Monitor DOM changes to prevent manual deletion */
    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();
    }

This method looks perfect, but the reality is cruel. When we look at the effect in the browser, we find that mutationobserver can't monitor the changes of shadow. what? After checking the data, it is found that shadow is independent of DOM tree, which is equivalent to isolated dom. In that case, let's give up pretending and force. Let's be honest, practical and normal DOM implementation. How many watermarks will generate multiple watermarked DOM, which will inevitably lead to performance problems. Well, let's find another way.

 

2, canvas drawing

1. Basic ideas

Draw a single watermark through canvas, use toDataURL method to generate base64 coding of the image, and put it in the background image: url() of the watermark container, and the background image will automatically cover the whole container. The idea is much simpler than the first scheme.

show me the code:

      var canvas = document.createElement('canvas')
      canvas.id = 'canvas'
      canvas.width = defaultSettings.watermark_width // Width of a single watermark
      canvas.height = defaultSettings.watermark_height // Height of a single watermark
      var ctx = canvas.getContext('2d')
      ctx.font = 'normal 12px Microsoft Yahei' // Set style
      ctx.fillStyle = 'rgba(112, 113, 114, 0.2)' // Watermark font color
      ctx.translate(canvas.width/2,canvas.height/2)
      ctx.rotate((defaultSettings.watermark_angle * Math.PI) / 180) // Watermark deflection angle
      ctx.translate(-canvas.width/2,-canvas.height/2)
      ctx.fillText(defaultSettings.watermark_txt, 30, 20)
      var src = canvas.toDataURL('image/png')

      /* Container for setting watermark */
      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'
      // Determine whether the container has set the backgroundImage property value
      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 + ')'
      }

PPT template downloadhttps://redbox.wode007.com

2. Optimize

The optimization idea is the same as the first scheme, but the advantage of this scheme is that it can directly monitor changes.

 

3, Comparison of two schemes

 

programme advantage shortcoming
shadow DOM  1. Low coupling, shadow dom is isolated from the original DOM tree, which will not affect the original functions of the system.

1. shadow DOM cannot be monitored

2. The cost of watermark copy and DOM tampering is low

3. The implementation logic is complex

canvas drawing

1. The implementation logic is relatively clear

2. Watermark data generates pictures, which is difficult for users to tamper with

3. The watermark is tampered with and can be monitored

 

1. The watermark image is placed in the background image. If the background image attribute is set in the class, it will be overwritten

Tags: Front-end

Posted by RobReid on Fri, 06 May 2022 09:24:58 +0300