A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

canvas 生成方式canvas 有着不错的兼容性,是一种比较可靠、成熟的可视化技术。但是它比较依赖分辨率,对文本的处理上也有着先天的不足。但是它可以很方便的将结果保存为图片,对于完成水印的需求也是非常合适的。


为了方便使用者上手,我将所有的实现坐标都设置为top/left,以方便对x、y的设置。
export default class CanvasWay {    constructor(watermark) {        this.watermark = watermark        const {width, height} = watermark        this.canvas = document.createElement('canvas');        this.canvas.setAttribute('width', width);        this.canvas.setAttribute('height', height);    }    render() {        const {txt, x, y, width, height, font, color, fontSize, alpha, angle} = this.watermark        const ctx = this.canvas.getContext('2d');        ctx.clearRect(0, 0, width, height);        ctx.textBaseline = 'top';        ctx.textAlign = 'left'        ctx.fillStyle = color;        ctx.globalAlpha = alpha;        ctx.font = `${fontSize}px ${font}`        ctx.translate(x, y)        ctx.rotate(Math.PI / 180 * angle);        ctx.translate(-x, -y - fontSize)        ctx.fillText(txt, x, y + fontSize);        return this.canvas.toDataURL();    }}复制代码svg 生成方式svg 与 canvas 相比浏览器兼容性几乎一致,除了几个早期的 Android 版本,这样的设备以及很难找到了,完全可以忽略。svg 使用的是 XML 的方式,不依赖分辨率,在做文本水印这件事上 svg 有着更好的优势。


svg 的 text 属性 x、y,是将文本左下位置定位到其坐标系的(x,y)位置,这可能和日常写 css 的定位不同,所有需要有一个 dy 值,设置其偏移量。
export default class SvgWay {    constructor(watermark) {        this.watermark = watermark    }    render() {        const {txt, x, y, width, height, color, font, fontSize, alpha, angle} = this.watermark        const svgStr =            `<svg xmlns="http://www.w3.org/2000/svg" width="${width}px" height="${height}px">                <text x="${x}px" y="${y}px" dy="${fontSize}px"                    text-anchor="start"                    stroke="${color}"                    stroke-opacity="${alpha}"                    fill="none"                    transform="rotate(${angle},${x} ${y})"                    font-weight="100"                    font-size="${fontSize}"                    font-family="${font}"                    >                    ${txt}                </text>            </svg>`;        return `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svgStr)))}`;    }}复制代码element 生成方式使用元素生成是一种很传统的方式,在本次实践中,我并没有考虑兼容性,因为使用了 CSS3 的属性 transform 所以在ie9 以下也是不能胜任的,但是因为有这种方式的实现,我们可以根据需求在后续补充不同的浏览器 Hack,如:filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); 使用这些代码,就可以完整覆盖到更多的浏览器。
import bindCSS from '../helpers/bindCSS'export default class ElementWay {    constructor(watermark) {        this.watermark = watermark    }    _createItem() {        let {txt, x, y, font, color, fontSize, alpha, angle, width, height} = this.watermark        const item = document.createElement('div');        bindCSS(item, {            position: 'relative',            width, height,            flex: `0 0 ${width}px`,            overflow: 'hidden',            pointerEvents: 'none'        })        let span = document.createElement('span');        span.innerHTML = txt        bindCSS(span, {            position: 'absolute',            top: `${y}px`,            left: `${x}px`,            fontFamily: font,            fontSize: `${fontSize}px`,            color: color,            lineHeight: 1.5,            opacity: alpha,            fontWeight: 400,            transform: `rotate(${angle}deg)`,            transformOrigin: '0 0',            userSelect: 'none',            whiteSpace: 'nowrap',            overflow: 'hidden'        })        item.appendChild(span)        return item;    }    render() {        const {width, height} = this.watermark        const {clientWidth, clientHeight} = document.documentElement || document.body        const column = Math.ceil(clientWidth / width)        const rows = Math.ceil(clientHeight / height)        const wrap = document.createElement('div');        bindCSS(wrap, {            display: 'flex',            flexWrap: 'wrap',            width: `${width * column}px`,            height: `${height * rows}px`        })        for (let i = 0; i < column * rows; i++) {            wrap.appendChild(this._createItem());        }        return wrap;    }}复制代码MutationObserver 元素异动监控MutationObserver 对现代浏览的兼容性还是不错的,MutationObserver是变动观察器,字面上就可以理解这是用来观察Node(节点)变化的。MutationObserver是在DOM4规范中定义的,它的前身是MutationEvent事件,最低支持版本为 ie9 ,目前已经被弃用。


在这里我们主要观察的有三点
  • 水印元素本身是否被移除
  • 水印元素属性是否被篡改(display:none ...)
  • 水印元素的子元素是否被移除和篡改 (主要考虑 element的实现方式 )
为了更少的触发观察者,我写了两个配置,第一个针对水印元素本身 {characterData: true, attributes: true, childList: true, subtree: true} 对所有的异动处理,第二个观察者主要对 body 下的 {childList: true} 敏感,也就是子元素的改变,在 body 观察者的回调只对有 removedNodes 这件事做出反应。
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;export const observer = (callback) => {    if (!MutationObserver) return false    let bodyObserver = new MutationObserver(        mutationsList => mutationsList.forEach(mutation =>            mutation.removedNodes.forEach(                _target => _target.id === _id && callback()            )        )    )    bodyObserver.observe(document.body, {childList: true});    const target = document.getElementById(_id);    let observer = new MutationObserver(callback);    observer.observe(target, {characterData: true, attributes: true, childList: true, subtree: true});    return {bodyObserver, observer};}复制代码最终工具调用方式    gwm.creation({        mode: 'svg',        watch: false,        fontSize: 13,        color: '#000',        font: 'sans-serif',        alpha: 0.2,        angle: -15    })

作者:LoadChange
链接:https://juejin.im/post/5b61a273e51d45349e11aba8



1 个回复

倒序浏览
奈斯
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马