Home >Web Front-end >JS Tutorial >Do you know how the front-end implements watermarking?
In daily work, we often encounter a lot of sensitive data. In order to prevent data leakage, we need to do some "packaging" on the data. The purpose is to make those "criminal elements" who are interested in leaking data give up their illegal behavior under severe "pressure of public opinion" and make them "criminal attempts" to achieve the effect of defeating others without fighting.
Those of us who work in the security department, the concept of data security has long been deeply rooted in our bones. Every word and every picture must be mindful of whether there is a risk of leakage. How to prevent data leakage is a question we have been thinking about. For example, the watermark of pictures is an issue we often encounter in our work process. Because my job content is the development of the review platform, some risky pictures often appear on the review platform. Considering that the security awareness of reviewers is uneven, in order to prevent unsafe things from happening, it is necessary to add watermarks to the pictures. of.
First of all, considering the business scenario, the problem at this stage is just worrying about data leakage during the review process. We only consider explicit watermarks for the time being, and adding some to the picture can Text or other data that identifies you personally. In this way, individuals can be traced based on the leaked data. Of course, its most important function is to take precautions and prevent problems before they occur.
Implementation methods
There are many ways to implement watermarks, which can be divided into Front-end watermarking and back-end watermarking. The advantages of front-end watermarking can be summarized in three points. First, it does not occupy server resources and relies entirely on the computing power of the client, reducing pressure on the server. Second, it is fast. No matter which front-end implementation is implemented, the performance is better than the back-end. Third, the implementation is simple. The biggest advantage of implementing watermarking on the backend can also be summarized in three points, namely safety, safety, and safety. Zhihu and Weibo both use back-end watermark solutions. However, after comprehensive consideration, we still adopt the front-end solution to implement watermarking. The following will also briefly introduce how nodejs implements back-end image watermarking.
node implementation
Provides three npm packages. This part is not the focus of our article, only a simple demo is provided.
1, gm https://github.com/aheckmann/gm 6.4k star
const fs = require('fs'); const gm = require('gm'); gm('/path/to/my/img.jpg') .drawText(30, 20, "GMagick!") .write("/path/to/drawing.png", function (err) { if (!err) console.log('done'); });
Need to install GraphicsMagick or ImageMagick;
2, node-images: https: //github.com/zhangyuanwei/node-images
const wrap = document.querySelector('#ReactApp'); const { clientWidth, clientHeight } = wrap; const waterHeight = 120; const waterWidth = 180; // 计算个数 const [columns, rows] = [~~(clientWidth / waterWidth), ~~(clientHeight / waterHeight)] for (let i = 0; i < columns; i++) { for (let j = 0; j <= rows; j++) { const waterDom = document.createElement('div'); // 动态设置偏移值 waterDom.setAttribute('style', ` width: ${waterWidth}px; height: ${waterHeight}px; left: ${waterWidth + (i - 1) * waterWidth + 10}px; top: ${waterHeight + (j - 1) * waterHeight + 10}px; color: #000; position: absolute` ); waterDom.innerText = '测试水印'; wrap.appendChild(waterDom); } }
No need to install other tools, lightweight, developed by zhangyuanwei Chinese, Chinese documentation;
3, jimp: https://github .com/oliver-moran/jimp
can be used with gifwrap to implement gif watermark;
Front-end implementation
1, background image to achieve full-screen watermark
You can check the effect on the personal information page inside and outside Alibaba
Advantages: The pictures are generated by the backend and are safe;
Disadvantages: You need to initiate an http request to obtain the picture information;
Effect display: Since it is an internal system, it is not convenient to display the effect.
2, DOM implements full image watermark and image watermark
Get the image width and height in the onload event of the image, generate the watermark area according to the image size, block it on the upper layer of the image, and the DOM content is watermarked Copywriting or other information can be implemented in a relatively simple way.
const wrap = document.querySelector('#ReactApp'); const { clientWidth, clientHeight } = wrap; const waterHeight = 120; const waterWidth = 180; // 计算个数 const [columns, rows] = [~~(clientWidth / waterWidth), ~~(clientHeight / waterHeight)] for (let i = 0; i < columns; i++) { for (let j = 0; j <= rows; j++) { const waterDom = document.createElement('p'); // 动态设置偏移值 waterDom.setAttribute('style', ` width: ${waterWidth}px; height: ${waterHeight}px; left: ${waterWidth + (i - 1) * waterWidth + 10}px; top: ${waterHeight + (j - 1) * waterHeight + 10}px; color: #000; position: absolute` ); waterDom.innerText = '测试水印'; wrap.appendChild(waterDom); } }
Advantages: simple and easy to implement;
Disadvantages: too large or too many pictures will affect performance;
3, canvas implementation method (first version implementation plan)
Method 1: Operate directly on the picture
No more nonsense, just go to the code
useEffect(() => { // gif 图不支持 if (src && src.includes('.gif')) { setShowImg(true); } image.onload = function () { try { // 太小的图不加载水印 if (image.width < 10) { setIsDataError(true); props.setIsDataError && props.setIsDataError(true); return; } const canvas = canvasRef.current; canvas.width = image.width; canvas.height = image.height; // 设置水印 const font = `${Math.min(Math.max(Math.floor(innerCanvas.width / 14), 14), 48)}px` || fontSize; innerContext.font = `${font} ${fontFamily}`; innerContext.textBaseline = 'hanging'; innerContext.rotate(rotate * Math.PI / 180); innerContext.lineWidth = lineWidth; innerContext.strokeStyle = strokeStyle; innerContext.strokeText(text, 0, innerCanvas.height / 4 * 3); innerContext.fillStyle = fillStyle; innerContext.fillText(text, 0, innerCanvas.height / 4 * 3); const context = canvas.getContext('2d'); context.drawImage(this, 0, 0); context.rect(0, 0, image.width || 200, image.height || 200); // 设置水印浮层 const pattern = context.createPattern(innerCanvas, 'repeat'); context.fillStyle = pattern; context.fill(); } catch (err) { console.info(err); setShowImg(true); } }; image.onerror = function () { setShowImg(true); }; }, [src]);
Advantages: Pure front-end implementation, the picture copied by right-clicking will also have a watermark ;
Disadvantages: GIF is not supported, images must support cross-domain;
Effect display: given below.
Method 2: canvas generates watermark url and assigns it to css background attribute
export const getBase64Background = (props) => { const { nick, empId } = GlobalConfig.userInfo; const { rotate = -20, height = 75, width = 85, text = `${nick}-${empId}`, fontSize = '14px', lineWidth = 2, fontFamily = 'microsoft yahei', strokeStyle = 'rgba(255, 255, 255, .15)', fillStyle = 'rgba(0, 0, 0, 0.15)', position = { x: 30, y: 30 }, } = props; const image = new Image(); image.crossOrigin = 'Anonymous'; const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.width = width; canvas.height = height; context.font = `${fontSize} ${fontFamily}`; context.lineWidth = lineWidth; context.rotate(rotate * Math.PI / 180); context.strokeStyle = strokeStyle; context.fillStyle = fillStyle; context.textAlign = 'center'; context.textBaseline = 'hanging'; context.strokeText(text, position.x, position.y); context.fillText(text, position.x, position.y); return canvas.toDataURL('image/png'); }; // 使用方式 <img src="https://xxx.xxx.jpg" /> <p className="warter-mark-area" style={{ backgroundImage: `url(${getBase64Background({})})` }} />
Advantages: pure front-end implementation, supports cross-domain, supports git image watermark;
Disadvantages: generation The base64 url is relatively large;
In fact, based on the implementation of these two canvases, you can easily come up with the third way, which is to cover the upper layer of the picture with a non-picture canvas in the first method, so that This will perfectly avoid the shortcomings of both options. But stop for a moment and think about it. Is there a simpler and easier way to combine the two solutions and use canvas to draw? Yes, use svg instead.
4, SVG method (the solution being used)
Gives a react version of the watermark component.
export const WaterMark = (props) => { // 获取水印数据 const { nick, empId } = GlobalConfig.userInfo; const boxRef = React.createRef(); const [waterMarkStyle, setWaterMarkStyle] = useState('180px 120px'); const [isError, setIsError] = useState(false); const { src, text = `${nick}-${empId}`, height: propsHeight, showSrc, img, nick, empId } = props; // 设置背景图和背景图样式 const boxStyle = { backgroundSize: waterMarkStyle, backgroundImage: `url("data:image/svg+xml;utf8,<svg width=\'100%\' height=\'100%\' xmlns=\'http://www.w3.org/2000/svg\' version=\'1.1\'><text width=\'100%\' height=\'100%\' x=\'20\' y=\'68\' transform=\'rotate(-20)\' fill=\'rgba(0, 0, 0, 0.2)\' font-size=\'14\' stroke=\'rgba(255, 255, 255, .2)\' stroke-width=\'1\'>${text}</text></svg>")`, }; const onLoad = (e) => { const dom = e.target; const { previousSibling, nextSibling, offsetLeft, offsetTop, } = dom; // 获取图片宽高 const { width, height } = getComputedStyle(dom); if (parseInt(width.replace('px', '')) < 180) { setWaterMarkStyle(`${width} ${height.replace('px', '') / 2}px`); }; previousSibling.style.height = height; previousSibling.style.width = width; previousSibling.style.top = `${offsetTop}px`; previousSibling.style.left = `${offsetLeft}px`; // 加载 loading 隐藏 nextSibling.style.display = 'none'; }; const onError = (event) => { setIsError(true); }; return ( <p className={styles.water_mark_wrapper} ref={boxRef}> <p className={styles.water_mark_box} style={boxStyle} /> {isError ? <ErrorSourceData src={src} showSrc={showSrc} height={propsHeight} text="图片加载错误" helpText="点击复制图片链接" /> : ( <> <img onLoad={onLoad} referrerPolicy="no-referrer" onError={onError} src={src} alt="图片显示错误" /> <Icon className={styles.img_loading} type="loading" /> </> ) } </p> ); };
Advantages: supports gif watermarks, no cross-domain problems, uses repeat attribute, no insertion into dom process, no performance problems;
QA
Question 1:
If the dom of the watermark is deleted, won’t the picture have no watermark?
Answer:
You can use MutationObserver to monitor the water node. If the node is modified, the picture will be hidden;
Question 2:
Right click of the mouse Copy a picture?
Answer:
The right-click function is disabled for all pictures
Question 3:
What if the picture information is obtained from the network of the console?
Answer:
There is no good solution for this operation yet. It is recommended to use a back-end implementation solution
The watermark solution implemented on the front end is always only a temporary solution, and the business back-end implementation consumes server resources. In fact, the most ideal solution is to provide an independent watermark service. Although loading There will be a slight delay in the process, but relative to data security, millisecond-level delays are still acceptable, which ensures that the service stability of the business is not affected.
During the daily Q&A process, many business parties will come to me to discuss the risk points of watermark blocking. I can only reply to them with the importance of data security every time. Of course, the size of the watermark, Transparency and density are also being continuously optimized. I believe there will be a version that can not only function as a watermark, but also better solve the occlusion problem.
Recommended learning: html video tutorial
The above is the detailed content of Do you know how the front-end implements watermarking?. For more information, please follow other related articles on the PHP Chinese website!