提取当前 DOM 并将其打印为字符串,样式保持不变
我希望能够按原样获取我的 DOM,并将其转换为字符串。假设我打开检查器并对特定元素的 margin-left 属性进行更改。该更改应反映在我的字符串中。
该函数应正确获取当前应用于元素的所有样式(不包括默认值),并将它们包含在该元素的内联样式中。
我编写了一个“解决方案”,但事实证明它不够充分。webkit 中的
getMatchedCSSRules
函数非常挑剔,我无法确定为什么它有时有效,有时无效。因此,除非它 100% 有效,否则我希望避免使用此功能。同样,
getComputedStyle
函数也有自己的问题。如果使用检查器将此页面上的 #footer 元素更改为
7px solid red
而不是
7px solid black
,则当我在控制台中运行
getComputedStyle(document.getElementById('footer')).cssText
时,更改将反映出来,但它也会给我一系列继承的属性,这些属性从未被使用检查器的用户或页面上的样式表修改过。
我正在寻找一种适用于 webkit 的解决方案 - 跨浏览器兼容性目前不是问题。
谢谢!
我认为这可能是一个解决方案(我花了将近一整天的时间!)。
它返回一个表示任何元素的 DOM 的字符串, 其中除默认值外的所有外部样式都包含在“style”属性中, 并且不会永久修改该元素。
例如:
console.log(document.body.serializeWithStyles());
您可以在 Web Inspector 命令行中或从 body 元素中的脚本标记加载此代码,但不能在 head 元素中加载,因为它需要 document.body 的存在。
我已经在桌面 Safari 5 上对其进行了测试(我没有移动版本)。
其工作原理如下:
对于 DOM 中的每个元素:
1) 将表示内联样式的 style.cssText 属性的值缓存在数组中;
2) 调用元素上的 getComputedStyle;
3) 检查我们是否有与此元素的标签名称相对应的 css 默认值查找表;
4) 如果没有,则构建它;
5) 遍历结果,使用查找表查找哪些值是非默认值;
6) 将这些非默认样式值应用于元素。
然后将 outerHTML 存储为结果;
对于每个元素,从缓存中恢复内联样式;
返回先前存储的结果。
代码:
Element.prototype.serializeWithStyles = (function () {
// Mapping between tag names and css default values lookup tables. This allows to exclude default values in the result.
var defaultStylesByTagName = {};
// Styles inherited from style sheets will not be rendered for elements with these tag names
var noStyleTags = {"BASE":true,"HEAD":true,"HTML":true,"META":true,"NOFRAME":true,"NOSCRIPT":true,"PARAM":true,"SCRIPT":true,"STYLE":true,"TITLE":true};
// This list determines which css default values lookup tables are precomputed at load time
// Lookup tables for other tag names will be automatically built at runtime if needed
var tagNames = ["A","ABBR","ADDRESS","AREA","ARTICLE","ASIDE","AUDIO","B","BASE","BDI","BDO","BLOCKQUOTE","BODY","BR","BUTTON","CANVAS","CAPTION","CENTER","CITE","CODE","COL","COLGROUP","COMMAND","DATALIST","DD","DEL","DETAILS","DFN","DIV","DL","DT","EM","EMBED","FIELDSET","FIGCAPTION","FIGURE","FONT","FOOTER","FORM","H1","H2","H3","H4","H5","H6","HEAD","HEADER","HGROUP","HR","HTML","I","IFRAME","IMG","INPUT","INS","KBD","KEYGEN","LABEL","LEGEND","LI","LINK","MAP","MARK","MATH","MENU","META","METER","NAV","NOBR","NOSCRIPT","OBJECT","OL","OPTION","OPTGROUP","OUTPUT","P","PARAM","PRE","PROGRESS","Q","RP","RT","RUBY","S","SAMP","SCRIPT","SECTION","SELECT","SMALL","SOURCE","SPAN","STRONG","STYLE","SUB","SUMMARY","SUP","SVG","TABLE","TBODY","TD","TEXTAREA","TFOOT","TH","THEAD","TIME","TITLE","TR","TRACK","U","UL","VAR","VIDEO","WBR"];
// Precompute the lookup tables.
for (var i = 0; i < tagNames.length; i++) {
if(!noStyleTags[tagNames[i]]) {
defaultStylesByTagName[tagNames[i]] = computeDefaultStyleByTagName(tagNames[i]);
}
}
function computeDefaultStyleByTagName(tagName) {
var defaultStyle = {};
var element = document.body.appendChild(document.createElement(tagName));
var computedStyle = getComputedStyle(element);
for (var i = 0; i < computedStyle.length; i++) {
defaultStyle[computedStyle[i]] = computedStyle[computedStyle[i]];
}
document.body.removeChild(element);
return defaultStyle;
}
function getDefaultStyleByTagName(tagName) {
tagName = tagName.toUpperCase();
if (!defaultStylesByTagName[tagName]) {
defaultStylesByTagName[tagName] = computeDefaultStyleByTagName(tagName);
}
return defaultStylesByTagName[tagName];
}
return function serializeWithStyles() {
if (this.nodeType !== Node.ELEMENT_NODE) { throw new TypeError(); }
var cssTexts = [];
var elements = this.querySelectorAll("*");
for ( var i = 0; i < elements.length; i++ ) {
var e = elements[i];
if (!noStyleTags[e.tagName]) {
var computedStyle = getComputedStyle(e);
var defaultStyle = getDefaultStyleByTagName(e.tagName);
cssTexts[i] = e.style.cssText;
for (var ii = 0; ii < computedStyle.length; ii++) {
var cssPropName = computedStyle[ii];
if (computedStyle[cssPropName] !== defaultStyle[cssPropName]) {
e.style[cssPropName] = computedStyle[cssPropName];
}
}
}
}
var result = this.outerHTML;
for ( var i = 0; i < elements.length; i++ ) {
elements[i].style.cssText = cssTexts[i];
}
return result;
}
})();
您不能只执行 document.getElementsByTagName('body')[0].innerHTML 吗?当我在检查器中进行更改,然后在控制台中输入上述 javascript 时,它会返回更新后的 HTML。
编辑:我刚刚尝试将该脚本放入函数中并将其附加到 onclick 事件。在检查器中进行了一些更新,单击按钮,它就成功了:
HTML
<button onclick="printDOM()">Print DOM</button>
Javascript
function printDOM() {
console.log(document.getElementsByTagName('body')[0].innerHTML) ;
}
如果您想要捕获整个页面,那么获取所有非内联样式表并将它们内联会更容易。
接受的答案中的方法很棒,但速度很慢,并且会涉及整个文档。
我采用以下方法来捕获包含样式的页面:
-
document.documentElement.outerHTML;
-
从
document.styleSheets
API 获取所有样式表
类似:
function captureCss(){
var cssrules = "";
var sheets = document.styleSheets;
for(var i = 0; i<sheets.length; i++){
if(!sheets[i].disabled && sheets[i].href != null) { // or sheets[i].href.nodeName == 'LINK'
if(sheets[i].rules == null){ // can be null because of cross origin policy
try{
var fetched = XHR GET(sheets[i].href); // works nicely because it hits the cache
if(fetched){
cssrules += "<style>\n"+fetched+"\n</style>\n"
}
}catch(e){
console.log(e);
}
continue;
}
for(var j=0;j<sheets[i].rules.length;j++){
cssrules += "<style>\n"+sheets[i].rules[j].cssText+"\n</style>\n"
}
}
}
return cssrules;
}
-
将捕获的
cssrules
作为outerHtml
html 文本中标题的第一件事添加
这样,您将获得一个自包含样式的页面。
这显然不适用于部分内容。