目录
- 一、介绍
- 二、准备
- 三、⽬标
- 四、代码
- 五、完成
一、介绍
很多⽹站都会通过给图⽚添加⽔印的形式来标记图⽚来源,维护版权。前端⽣成⽔印通常是通过canvas 实现,但实际上我们也可以直接利⽤ CSS 来实现图⽚⽔印,这样做会有更好的浏览器兼容
性。
本题中你将封装⼀个创建⽂字⽔印的函数。
二、准备
开始答题前,需要先打开本题的项⽬代码⽂件夹,⽬录结构如下:
├── css
│ └── style.css
├── images
│ └── origin.png
├── index.html
└── js
├── dom-to-image.min.js
└── index.js
其中:
- index.html 是主⻚⾯。
- css/style.css 是样式⽂件。
- js/index.js 是需要补充代码的 js ⽂件。
- js/dom-to-image.min.js 是⽣成图⽚的第三⽅库,此⽂件⽆需修改。
- images/origin.png 是项⽬中的原始图⽚⽂件。
在浏览器中预览 index.html ⻚⾯效果如下所示
三、⽬标
请完善 js/index.js ⽂件中的 TODO 部分,实现创建⽔印函数的功能 ,创建的⽔印需要使⽤<span>
标签展示。
createWatermark 函数参数说明:
完成后的效果如下:
四、代码
index.htmll
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="css/style.css" />
<title>图片水印生成</title>
</head>
<body>
<main>
<div class="container">
<img src="images/origin.png" alt="cat" />
</div>
<button>保存图片</button>
</main>
<script src="js/dom-to-image.min.js"></script>
<script src="js/index.js"></script>
</body>
</html>
style.css
main {
display: flex;
align-items: center;
flex-direction: column;
}
.container {
width: 700px;
display: inline-block;
position: relative;
}
.container img {
width: 100%;
height: 100%;
}
.watermark {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
pointer-events: none;
white-space: nowrap;
overflow: hidden;
}
.watermark span {
line-height: 30px;
display: inline-block;
position: relative;
top: 50%;
transform: translateY(-50%);
font: "sans serif";
}
button {
display: block;
margin-top: 1rem;
border: none;
padding: 1rem 1.5rem;
cursor: pointer;
border-radius: 5px;
}
button:active {
background-color: gainsboro;
}
images—>origin.png
dom-to-image.min.js
/*!
* Powered by uglifiyJS v2.6.1, Build by http://tool.uis.cc/jsmin/
* build time: Tue Mar 21 2023 14:53:38 GMT+0800 (中国标准时间)
*/
!function(global){"use strict";function toSvg(node,options){function applyOptions(clone){return options.bgcolor&&(clone.style.backgroundColor=options.bgcolor),options.width&&(clone.style.width=options.width+"px"),options.height&&(clone.style.height=options.height+"px"),options.style&&Object.keys(options.style).forEach(function(property){clone.style[property]=options.style[property]}),clone}return options=options||{},copyOptions(options),Promise.resolve(node).then(function(node){return cloneNode(node,options.filter,!0)}).then(embedFonts).then(inlineImages).then(applyOptions).then(function(clone){return makeSvgDataUri(clone,options.width||util.width(node),options.height||util.height(node))})}function toPixelData(node,options){return draw(node,options||{}).then(function(canvas){return canvas.getContext("2d").getImageData(0,0,util.width(node),util.height(node)).data})}function toPng(node,options){return draw(node,options||{}).then(function(canvas){return canvas.toDataURL()})}function toJpeg(node,options){return options=options||{},draw(node,options).then(function(canvas){return canvas.toDataURL("image/jpeg",options.quality||1)})}function toBlob(node,options){return draw(node,options||{}).then(util.canvasToBlob)}function copyOptions(options){"undefined"==typeof options.imagePlaceholder?domtoimage.impl.options.imagePlaceholder=defaultOptions.imagePlaceholder:domtoimage.impl.options.imagePlaceholder=options.imagePlaceholder,"undefined"==typeof options.cacheBust?domtoimage.impl.options.cacheBust=defaultOptions.cacheBust:domtoimage.impl.options.cacheBust=options.cacheBust}function draw(domNode,options){function newCanvas(domNode){var canvas=document.createElement("canvas");if(canvas.width=options.width||util.width(domNode),canvas.height=options.height||util.height(domNode),options.bgcolor){var ctx=canvas.getContext("2d");ctx.fillStyle=options.bgcolor,ctx.fillRect(0,0,canvas.width,canvas.height)}return canvas}return toSvg(domNode,options).then(util.makeImage).then(util.delay(100)).then(function(image){var canvas=newCanvas(domNode);return canvas.getContext("2d").drawImage(image,0,0),canvas})}function cloneNode(node,filter,root){function makeNodeCopy(node){return node instanceof HTMLCanvasElement?util.makeImage(node.toDataURL()):node.cloneNode(!1)}function cloneChildren(original,clone,filter){function cloneChildrenInOrder(parent,children,filter){var done=Promise.resolve();return children.forEach(function(child){done=done.then(function(){return cloneNode(child,filter)}).then(function(childClone){childClone&&parent.appendChild(childClone)})}),done}var children=original.childNodes;return 0===children.length?Promise.resolve(clone):cloneChildrenInOrder(clone,util.asArray(children),filter).then(function(){return clone})}function processClone(original,clone){function cloneStyle(){function copyStyle(source,target){function copyProperties(source,target){util.asArray(source).forEach(function(name){target.setProperty(name,source.getPropertyValue(name),source.getPropertyPriority(name))})}source.cssText?target.cssText=source.cssText:copyProperties(source,target)}copyStyle(window.getComputedStyle(original),clone.style)}function clonePseudoElements(){function clonePseudoElement(element){function formatPseudoElementStyle(className,element,style){function formatCssText(style){var content=style.getPropertyValue("content");return style.cssText+" content: "+content+";"}function formatCssProperties(style){function formatProperty(name){return name+": "+style.getPropertyValue(name)+(style.getPropertyPriority(name)?" !important":"")}return util.asArray(style).map(formatProperty).join("; ")+";"}var selector="."+className+":"+element,cssText=style.cssText?formatCssText(style):formatCssProperties(style);return document.createTextNode(selector+"{"+cssText+"}")}var style=window.getComputedStyle(original,element),content=style.getPropertyValue("content");if(""!==content&&"none"!==content){var className=util.uid();clone.className=clone.className+" "+className;var styleElement=document.createElement("style");styleElement.appendChild(formatPseudoElementStyle(className,element,style)),clone.appendChild(styleElement)}}[":before",":after"].forEach(function(element){clonePseudoElement(element)})}function copyUserInput(){original instanceof HTMLTextAreaElement&&(clone.innerHTML=original.value),original instanceof HTMLInputElement&&clone.setAttribute("value",original.value)}function fixSvg(){clone instanceof SVGElement&&(clone.setAttribute("xmlns","http://www.w3.org/2000/svg"),clone instanceof SVGRectElement&&["width","height"].forEach(function(attribute){var value=clone.getAttribute(attribute);value&&clone.style.setProperty(attribute,value)}))}return clone instanceof Element?Promise.resolve().then(cloneStyle).then(clonePseudoElements).then(copyUserInput).then(fixSvg).then(function(){return clone}):clone}return root||!filter||filter(node)?Promise.resolve(node).then(makeNodeCopy).then(function(clone){return cloneChildren(node,clone,filter)}).then(function(clone){return processClone(node,clone)}):Promise.resolve()}function embedFonts(node){return fontFaces.resolveAll().then(function(cssText){var styleNode=document.createElement("style");return node.appendChild(styleNode),styleNode.appendChild(document.createTextNode(cssText)),node})}function inlineImages(node){return images.inlineAll(node).then(function(){return node})}function makeSvgDataUri(node,width,height){return Promise.resolve(node).then(function(node){return node.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),(new XMLSerializer).serializeToString(node)}).then(util.escapeXhtml).then(function(xhtml){return'<foreignObject x="0" y="0" width="100%" height="100%">'+xhtml+"</foreignObject>"}).then(function(foreignObject){return'<svg xmlns="http://www.w3.org/2000/svg" width="'+width+'" height="'+height+'">'+foreignObject+"</svg>"}).then(function(svg){return"data:image/svg+xml;charset=utf-8,"+svg})}function newUtil(){function mimes(){var WOFF="application/font-woff",JPEG="image/jpeg";return{woff:WOFF,woff2:WOFF,ttf:"application/font-truetype",eot:"application/vnd.ms-fontobject",png:"image/png",jpg:JPEG,jpeg:JPEG,gif:"image/gif",tiff:"image/tiff",svg:"image/svg+xml"}}function parseExtension(url){var match=/\.([^\.\/]*?)$/g.exec(url);return match?match[1]:""}function mimeType(url){var extension=parseExtension(url).toLowerCase();return mimes()[extension]||""}function isDataUrl(url){return-1!==url.search(/^(data:)/)}function toBlob(canvas){return new Promise(function(resolve){for(var binaryString=window.atob(canvas.toDataURL().split(",")[1]),length=binaryString.length,binaryArray=new Uint8Array(length),i=0;length>i;i++)binaryArray[i]=binaryString.charCodeAt(i);resolve(new Blob([binaryArray],{type:"image/png"}))})}function canvasToBlob(canvas){return canvas.toBlob?new Promise(function(resolve){canvas.toBlob(resolve)}):toBlob(canvas)}function resolveUrl(url,baseUrl){var doc=document.implementation.createHTMLDocument(),base=doc.createElement("base");doc.head.appendChild(base);var a=doc.createElement("a");return doc.body.appendChild(a),base.href=baseUrl,a.href=url,a.href}function uid(){var index=0;return function(){function fourRandomChars(){return("0000"+(Math.random()*Math.pow(36,4)<<0).toString(36)).slice(-4)}return"u"+fourRandomChars()+index++}}function makeImage(uri){return new Promise(function(resolve,reject){var image=new Image;image.onload=function(){resolve(image)},image.onerror=reject,image.src=uri})}function getAndEncode(url){var TIMEOUT=3e4;return domtoimage.impl.options.cacheBust&&(url+=(/\?/.test(url)?"&":"?")+(new Date).getTime()),new Promise(function(resolve){function done(){if(4===request.readyState){if(200!==request.status)return void(placeholder?resolve(placeholder):fail("cannot fetch resource: "+url+", status: "+request.status));var encoder=new FileReader;encoder.onloadend=function(){var content=encoder.result.split(/,/)[1];resolve(content)},encoder.readAsDataURL(request.response)}}function timeout(){placeholder?resolve(placeholder):fail("timeout of "+TIMEOUT+"ms occured while fetching resource: "+url)}function fail(message){resolve("")}var request=new XMLHttpRequest;request.onreadystatechange=done,request.ontimeout=timeout,request.responseType="blob",request.timeout=TIMEOUT,request.open("GET",url,!0),request.send();var placeholder;if(domtoimage.impl.options.imagePlaceholder){var split=domtoimage.impl.options.imagePlaceholder.split(/,/);split&&split[1]&&(placeholder=split[1])}})}function dataAsUrl(content,type){return"data:"+type+";base64,"+content}function escape(string){return string.replace(/([.*+?^${}()|\[\]\/\\])/g,"\\$1")}function delay(ms){return function(arg){return new Promise(function(resolve){setTimeout(function(){resolve(arg)},ms)})}}function asArray(arrayLike){for(var array=[],length=arrayLike.length,i=0;length>i;i++)array.push(arrayLike[i]);return array}function escapeXhtml(string){return string.replace(/#/g,"%23").replace(/\n/g,"%0A")}function width(node){var leftBorder=px(node,"border-left-width"),rightBorder=px(node,"border-right-width");return node.scrollWidth+leftBorder+rightBorder}function height(node){var topBorder=px(node,"border-top-width"),bottomBorder=px(node,"border-bottom-width");return node.scrollHeight+topBorder+bottomBorder}function px(node,styleProperty){var value=window.getComputedStyle(node).getPropertyValue(styleProperty);return parseFloat(value.replace("px",""))}return{escape:escape,parseExtension:parseExtension,mimeType:mimeType,dataAsUrl:dataAsUrl,isDataUrl:isDataUrl,canvasToBlob:canvasToBlob,resolveUrl:resolveUrl,getAndEncode:getAndEncode,uid:uid(),delay:delay,asArray:asArray,escapeXhtml:escapeXhtml,makeImage:makeImage,width:width,height:height}}function newInliner(){function shouldProcess(string){return-1!==string.search(URL_REGEX)}function readUrls(string){for(var match,result=[];null!==(match=URL_REGEX.exec(string));)result.push(match[1]);return result.filter(function(url){return!util.isDataUrl(url)})}function inline(string,url,baseUrl,get){function urlAsRegex(url){return new RegExp("(url\\(['\"]?)("+util.escape(url)+")(['\"]?\\))","g")}return Promise.resolve(url).then(function(url){return baseUrl?util.resolveUrl(url,baseUrl):url}).then(get||util.getAndEncode).then(function(data){return util.dataAsUrl(data,util.mimeType(url))}).then(function(dataUrl){return string.replace(urlAsRegex(url),"$1"+dataUrl+"$3")})}function inlineAll(string,baseUrl,get){function nothingToInline(){return!shouldProcess(string)}return nothingToInline()?Promise.resolve(string):Promise.resolve(string).then(readUrls).then(function(urls){var done=Promise.resolve(string);return urls.forEach(function(url){done=done.then(function(string){return inline(string,url,baseUrl,get)})}),done})}var URL_REGEX=/url\(['"]?([^'"]+?)['"]?\)/g;return{inlineAll:inlineAll,shouldProcess:shouldProcess,impl:{readUrls:readUrls,inline:inline}}}function newFontFaces(){function resolveAll(){return readAll(document).then(function(webFonts){return Promise.all(webFonts.map(function(webFont){return webFont.resolve()}))}).then(function(cssStrings){return cssStrings.join("\n")})}function readAll(){function selectWebFontRules(cssRules){return cssRules.filter(function(rule){return rule.type===CSSRule.FONT_FACE_RULE}).filter(function(rule){return inliner.shouldProcess(rule.style.getPropertyValue("src"))})}function getCssRules(styleSheets){var cssRules=[];return styleSheets.forEach(function(sheet){try{util.asArray(sheet.cssRules||[]).forEach(cssRules.push.bind(cssRules))}catch(e){}}),cssRules}function newWebFont(webFontRule){return{resolve:function(){var baseUrl=(webFontRule.parentStyleSheet||{}).href;return inliner.inlineAll(webFontRule.cssText,baseUrl)},src:function(){return webFontRule.style.getPropertyValue("src")}}}return Promise.resolve(util.asArray(document.styleSheets)).then(getCssRules).then(selectWebFontRules).then(function(rules){return rules.map(newWebFont)})}return{resolveAll:resolveAll,impl:{readAll:readAll}}}function newImages(){function newImage(element){function inline(get){return util.isDataUrl(element.src)?Promise.resolve():Promise.resolve(element.src).then(get||util.getAndEncode).then(function(data){return util.dataAsUrl(data,util.mimeType(element.src))}).then(function(dataUrl){return new Promise(function(resolve,reject){element.onload=resolve,element.onerror=reject,element.src=dataUrl})})}return{inline:inline}}function inlineAll(node){function inlineBackground(node){var background=node.style.getPropertyValue("background");return background?inliner.inlineAll(background).then(function(inlined){node.style.setProperty("background",inlined,node.style.getPropertyPriority("background"))}).then(function(){return node}):Promise.resolve(node)}return node instanceof Element?inlineBackground(node).then(function(){return node instanceof HTMLImageElement?newImage(node).inline():Promise.all(util.asArray(node.childNodes).map(function(child){return inlineAll(child)}))}):Promise.resolve(node)}return{inlineAll:inlineAll,impl:{newImage:newImage}}}var util=newUtil(),inliner=newInliner(),fontFaces=newFontFaces(),images=newImages(),defaultOptions={imagePlaceholder:void 0,cacheBust:!1},domtoimage={toSvg:toSvg,toPng:toPng,toJpeg:toJpeg,toBlob:toBlob,toPixelData:toPixelData,impl:{fontFaces:fontFaces,images:images,util:util,inliner:inliner,options:{}}};"undefined"!=typeof module?module.exports=domtoimage:global.domtoimage=domtoimage}(this);
index.js
/**
* 创建一个文字水印的div
* @param {string} text - 水印文字
* @param {string} color - 水印颜色
* @param {number} deg - 水印旋转角度
* @param {number} opacity - 水印透明度
* @param {number} count - 水印数量
*/
function createWatermark(text, color, deg, opacity, count) {
// 创建水印容器
const container = document.createElement('div')
container.className = 'watermark'
// TODO: 根据输入参数创建文字水印
return container
}
// 以下代码不需要修改
// 调用createWatermark方法,创建图片水印
const watermark = createWatermark('WaterMark', 'white', 45, 0.5, 11)
// 将水印挂载到图片容器上
const container = document.querySelector('.container')
container.appendChild(watermark)
// 提供图片保存功能
const button = document.querySelector('button')
button.addEventListener('click', () => {
domtoimage.toJpeg(document.querySelector('.container')).then((dataUrl) => {
const link = document.createElement('a')
link.download = 'image.jpeg'
link.href = dataUrl
link.click()
})
})
五、完成
index.js
/**
* 创建一个文字水印的div
* @param {string} text - 水印文字
* @param {string} color - 水印颜色
* @param {number} deg - 水印旋转角度
* @param {number} opacity - 水印透明度
* @param {number} count - 水印数量
*/
function createWatermark(text, color, deg, opacity, count) {
// 创建水印容器
const container = document.createElement('div')
container.className = 'watermark'
// TODO: 根据输入参数创建文字水印
for (let i = 0; i < count; i++) {
const span = document.createElement('span')
span.style.color = color
span.style.transform = `rotate(${deg}deg)`
span.style.opacity = opacity
span.innerText += text
container.appendChild(span)
}
return container
}