水印项目我们提出了两种解决方案
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;
}))
思路:通过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把单个水印绘制出来,用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 + ')'
}
优化思路和第一种方案一样,只不过这种方案的好处就是可以直接监听到变化。
方案 | 优点 | 缺点 |
shadow DOM | 1、低耦合,shadow dom与原先的DOM树隔离,不会影响到系统本有的功能。 | 1、shadow DOM不能够被监听到 2、水印文案及DOM被篡改的成本较低 3、实现逻辑比较复杂 |
canvas绘图 | 1、实现逻辑比较清晰 2、水印数据生成图片,用户想篡改比较难 3、水印被篡改能够被监听到
| 1、水印图片是被放在background-image里面的,如果原先在class里面设置了background-image属性的话会被覆盖掉 |
在当今的专业环境中,项目经理需要戴上各种帽子,在管理团队的日常功能和理解大局策略之间切换。正因为如此,项目经理对组织变得更有价值,并且他们对技能和战略角色的需求在全球范围内不断增长。但这也提出了一个问题:如何在如此高压的环境中成为更好的项目经理?
随机产生规定范围内的整数,然后再产生相同范围内的整数,两者相同时,则暂停。所用知识:Math.random() * num: 产生从0到num的随机数,Math.floor(): 向下取整,简单的DOM操作等
我马上就要毕业了没有开发经验怎么办?我投递了 N 多公司全部没有给工作机会,有的给了面试机会也是没有下文了怎么办?我简历上什么东西都没有,要不要伪造一个工作经历呢?
项目经理这个神奇的职位,改变了我很多工作处事的方式,从前性情纯真的耿直boy,现在变成了人鬼皆爱的老油条, 以下是我当了项目经理之后明白的10件事, 如有雷同,真是太巧。
pm2 大家应该都知道,主要是用来管理 node 进程,但是同样可以用来部署前端代码。也可以手动添加 public key 到服务器上的 ~/.ssh/authorized_keys,
我不是专业的项目经理,这里不讨论大型项目管理的事情。我们比较常遇到的可能是小型的长周期项目,比如2-4个人,做半年甚至一年的项目。这种项目通常不会有专职的项目经理
我相信每个接受过老项目的程序员可能都吐槽过“前人的代码都是屎”。一个已经有些年头的项目,几乎肯定可以看到——到处拷贝来拷贝去的代码,随处可见的拼写错误,头重脚轻的函数……
近几年随着微服务化项目的崛起,逐渐成为许多公司中大型分布式系统架构的主流方式,而今天所说的 RPC 在这其中扮演着至关重要的角色。随着这段日子公司项目微服务化的演进,发现在日常开发中都在隐式或显式的使用 RPC
首先搭建vue项目,lint选择ESLint + Prettier,配置方式选择In dedicated config files。具体搭建过程这里就不赘述了,如果不熟悉的同学可以点击这里。配置 Stylelint,目前还没有stylelint选项,需要我们自己安装相关的 npm 包
created : 中请求数据,ajax是异步的,这个时候可能mounted已经执行完了,也就是dom挂载完了,但数据还没请求回来,无法获取到内部元素(数据渲染出来的dom)高度. 无法渲染内部元素,无法滚动
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!