CSSをインライン展開したいという需要がある。
例えばHTMLメールを送る場合、外部CSSは解釈されない、headタグ内のstyleタグも解釈されない、各タグのstyle属性に記述するのが一番だ、というようなシチュエーションがある。
そんなわけで、CSSを簡単にインライン展開できないものかと調べていた。
先人が様々な試みをしているが、
- http://premailer.dialect.ca/api
- なにやら502を連発したり展開の仕方が安定しない
- 実装は https://github.com/alexdunae/premailer っぽい
- http://inlinestyler.torchboxapps.com/styler/convert/
- 外部CSSが見つからないとかいうので早々に投げた
- CSS::Inlinerを使ってみる
- 文字化けするし変なstyleになるし使いものにならない
- http://stackoverflow.com/questions/1271438/how-can-i-merge-css-definitions-in-files-into-inline-style-attributes-using-per
- 依存ライブラリがエラーを吐いて動かない、修正して動かしても変なHTMLが吐き出される
- http://sedition.com/a/156
- CSS::Tiny->readの時点でコケるので投げた
というような感じで、複雑なCSSをぶち込むとまともに動きそうなものが簡単には見つからない。
そういったところで、ふと「PhantomJSを使ってComputed Styleを取得すれば完璧なんじゃね?」という天啓を授かったので、やってみた。
結論から言うと、まだきちんとできてないです。
var page = require('webpage').create(); var url = 'http://www.phantomjs.org/'; page.open(url, function (status) { //Page is loaded! var style = page.evaluate(function () { var e = document.getElementsByTagName('span')[0]; var style = window.getComputedStyle(e, null); return style; }); var cssText = style.cssText cssText = cssText.replace(/;/g, ";\n"); cssText = cssText.replace(/ +/g, ""); console.log(cssText); phantom.exit(); });
このコードを実行すると、
background-attachment:scroll; background-clip:border-box; background-color:rgba(0,0,0,0); background-image:none; background-origin:padding-box; background-position:0%0%; background-repeat:repeat; background-size:autoauto; border-bottom-color:rgb(255,255,255); border-bottom-left-radius:0px; border-bottom-right-radius:0px; border-bottom-style:none; border-bottom-width:0px; border-collapse:separate; border-left-color:rgb(255,255,255); border-left-style:none; border-left-width:0px; border-right-color:rgb(255,255,255); border-right-style:none; border-right-width:0px; border-top-color:rgb(255,255,255); border-top-left-radius:0px; border-top-right-radius:0px; border-top-style:none; border-top-width:0px; bottom:auto; box-shadow:none; box-sizing:content-box; caption-side:top; clear:none; clip:auto; color:rgb(255,255,255); cursor:auto; direction:ltr; display:inline; empty-cells:show; float:none; font-family:'DroidSans','LucidaGrande',sans-serif; font-size:24px; font-style:normal; font-variant:normal; font-weight:normal; height:0px; left:auto; letter-spacing:normal; line-height:54px; list-style-image:none; list-style-position:outside; list-style-type:disc; margin-bottom:0px; margin-left:0px; margin-right:0px; margin-top:0px; max-height:none; max-width:none; min-height:0px; min-width:0px; opacity:1; orphans:2; outline-color:rgb(255,255,255); outline-style:none; outline-width:0px; overflow-x:visible; overflow-y:visible; padding-bottom:0px; padding-left:0px; padding-right:0px; padding-top:0px; page-break-after:auto; page-break-before:auto; page-break-inside:auto; pointer-events:auto; position:static; resize:none; right:auto; speak:normal; table-layout:auto; text-align:-webkit-auto; text-decoration:none; text-indent:0px; text-rendering:auto; text-shadow:rgb(61,151,0)0px-1px0px; text-overflow:clip; text-transform:none; top:auto; unicode-bidi:normal; vertical-align:baseline; visibility:visible; white-space:normal; widows:2; width:0px; word-break:normal; word-spacing:0px; word-wrap:normal; z-index:auto; zoom:1; -webkit-animation-delay:0s; -webkit-animation-direction:normal; -webkit-animation-duration:0s; -webkit-animation-fill-mode:none; -webkit-animation-iteration-count:1; -webkit-animation-name:none; -webkit-animation-play-state:running; -webkit-animation-timing-function:cubic-bezier(0.25,0.1,0.25,1); -webkit-appearance:none; -webkit-backface-visibility:visible; -webkit-background-clip:border-box; -webkit-background-composite:source-over; -webkit-background-origin:padding-box; -webkit-background-size:autoauto; -webkit-border-fit:border; -webkit-border-horizontal-spacing:0px; -webkit-border-image:none; -webkit-border-vertical-spacing:0px; -webkit-box-align:stretch; -webkit-box-direction:normal; -webkit-box-flex:0; -webkit-box-flex-group:1; -webkit-box-lines:single; -webkit-box-ordinal-group:1; -webkit-box-orient:horizontal; -webkit-box-pack:start; -webkit-box-reflect:none; -webkit-box-shadow:none; -webkit-color-correction:default; -webkit-column-break-after:auto; -webkit-column-break-before:auto; -webkit-column-break-inside:auto; -webkit-column-count:auto; -webkit-column-gap:normal; -webkit-column-rule-color:rgb(255,255,255); -webkit-column-rule-style:none; -webkit-column-rule-width:0px; -webkit-column-span:1; -webkit-column-width:auto; -webkit-font-smoothing:auto; -webkit-highlight:none; -webkit-hyphenate-character:auto; -webkit-hyphenate-limit-after:auto; -webkit-hyphenate-limit-before:auto; -webkit-hyphens:manual; -webkit-line-box-contain:blockinlinereplaced; -webkit-line-break:normal; -webkit-line-clamp:none; -webkit-locale:auto; -webkit-margin-before-collapse:collapse; -webkit-margin-after-collapse:collapse; -webkit-marquee-direction:auto; -webkit-marquee-increment:6px; -webkit-marquee-repetition:infinite; -webkit-marquee-style:scroll; -webkit-mask-attachment:scroll; -webkit-mask-box-image:none; -webkit-mask-clip:border-box; -webkit-mask-composite:source-over; -webkit-mask-image:none; -webkit-mask-origin:border-box; -webkit-mask-position:0%0%; -webkit-mask-repeat:repeat; -webkit-mask-size:autoauto; -webkit-nbsp-mode:normal; -webkit-perspective:none; -webkit-perspective-origin:0px0px; -webkit-rtl-ordering:logical; -webkit-text-combine:none; -webkit-text-decorations-in-effect:none; -webkit-text-emphasis-color:rgb(255,255,255); -webkit-text-emphasis-position:over; -webkit-text-emphasis-style:none; -webkit-text-fill-color:rgb(255,255,255); -webkit-text-orientation:vertical-right; -webkit-text-security:none; -webkit-text-stroke-color:rgb(255,255,255); -webkit-text-stroke-width:0px; -webkit-transform:none; -webkit-transform-origin:0px0px; -webkit-transform-style:flat; -webkit-transition-delay:0s; -webkit-transition-duration:0s; -webkit-transition-property:all; -webkit-transition-timing-function:cubic-bezier(0.25,0.1,0.25,1); -webkit-user-drag:auto; -webkit-user-modify:read-only; -webkit-user-select:text; -webkit-writing-mode:horizontal-tb; clip-path:none; clip-rule:nonzero; mask:none; filter:none; flood-color:rgb(0,0,0); flood-opacity:1; lighting-color:rgb(255,255,255); stop-color:rgb(0,0,0); stop-opacity:1; color-interpolation:srgb; color-interpolation-filters:linearrgb; color-rendering:auto; fill:#000000; fill-opacity:1; fill-rule:nonzero; image-rendering:auto; marker-end:none; marker-mid:none; marker-start:none; shape-rendering:auto; stroke:none; stroke-dasharray:none; stroke-dashoffset:0; stroke-linecap:butt; stroke-linejoin:miter; stroke-miterlimit:4; stroke-opacity:1; stroke-width:1; alignment-baseline:auto; baseline-shift:baseline; dominant-baseline:auto; kerning:0; text-anchor:start; writing-mode:lr-tb; glyph-orientation-horizontal:0deg; glyph-orientation-vertical:auto; -webkit-svg-shadow:none; vector-effect:none;
このような実行結果を得られる。
これだと読む気が失せるので、予め空のdivのcssTextをnothing-css.txt
とかに出力しておいて、
diff <(phantomjs get-css-text.js) nothing-css.txt
とか実行すると、
9c9 < border-bottom-color:rgb(255,255,255); --- > border-bottom-color:rgb(0,0,0); 15c15 < border-left-color:rgb(255,255,255); --- > border-left-color:rgb(0,0,0); 18c18 < border-right-color:rgb(255,255,255); --- > border-right-color:rgb(0,0,0); 21c21 < border-top-color:rgb(255,255,255); --- > border-top-color:rgb(0,0,0); 32c32 < color:rgb(255,255,255); --- > color:rgb(0,0,0); 35c35 < display:inline; --- > display:block; 38,39c38,39 < font-family:'DroidSans','LucidaGrande',sans-serif; < font-size:24px; --- > font-family:'TimesNewRoman'; > font-size:16px; 46c46 < line-height:54px; --- > line-height:normal; 60c60 < outline-color:rgb(255,255,255); --- > outline-color:rgb(0,0,0); 82c82 < text-shadow:rgb(61,151,0)0px-1px0px; --- > text-shadow:none; 91c91 < width:0px; --- > width:384px; 131c131 < -webkit-column-rule-color:rgb(255,255,255); --- > -webkit-column-rule-color:rgb(0,0,0); 163c163 < -webkit-perspective-origin:0px0px; --- > -webkit-perspective-origin:192px0px; 167c167 < -webkit-text-emphasis-color:rgb(255,255,255); --- > -webkit-text-emphasis-color:rgb(0,0,0); 170c170 < -webkit-text-fill-color:rgb(255,255,255); --- > -webkit-text-fill-color:rgb(0,0,0); 173c173 < -webkit-text-stroke-color:rgb(255,255,255); --- > -webkit-text-stroke-color:rgb(0,0,0); 176c176 < -webkit-transform-origin:0px0px; --- > -webkit-transform-origin:192px0px;
という感じでそれなりにヒューマンリーダブルになる。
しかしながら残された問題として、あくまで取得したいのは「インライン展開されたCSS」であって「計算された結果のCSS」ではないので、この方法で得られるCSSは冗長すぎるというところがある。
Google ChromeのInspectorとかだとShow inheritedみたいなトグルオプションがあるけど、まさにああいう感じで取得したい。
僕はJavaScriptもPhantomJSも知らんという人間なのでもしかたら上手に取得できる方法があるのかも知れないけれど、簡単に調べてみた感じではその方法は分からなかった。
そんなわけで、誰か良いソリューションあったら教えて下さい。疲れた。