[ カテゴリー » 開発日誌 » JavaScript ]

カラーキーワードをHEXやRGBに変換したい

category-icon

 こんばんは。久しぶりにDean EdwardsLink のブログを覗いたら、MSIEでカラーキーワード(redとかtomatoとか)やらをHEX方式(#f01234とか)に変換するやり方が載ってました。

function toHex(color) {
  var body  = createPopup().document.body,
  range = body.createTextRange();
  body.style.color = color;
  var value = range.queryCommandValue("ForeColor");
  value = ((value & 0x0000ff) << 16) | (value & 0x00ff00) | ((value & 0xff0000) >>> 16);
  value = value.toString(16);
  return "#000000".slice(0, 7 - value.length) + value;
};
へぇ。これにも元ネタがあって、それはerik's weblogのコメントLink です。

 ppBlogには、cornerplay.jsというJavaScriptがあって、指定ボックスの角を丸くしたり凸凹にしたりできる動作を担当しています。具体的には以下のような感じです。

 こんな感じで、コーナーをちょいと変えると、見た目のアクセントになります。

 で、このボックスの背景色に、tomatoとかindigoとかlavenderなどのいわゆるカラーキーワードLink でも指定できるように、これらカラーキーワードをHEX方式に直した対応表も含めているんですよね(147色ぐらいある)。今、考えるとほかにもやりようがある気もしますが、当時は仕方ないなぁとその対応表をJSON配列にして含めてました。

 このDean Edwardsのやり方なら、対応表なんていりませんね。記述が短くなってファイルサイズも小さくなるだろうし言うことないです。ただ、これはIE限定なので、Firefoxやその他のブラウザでも動くように改良してみましょう。Deanは、「ほかのブラウザは、getComputedStyle()が使えるからバッチリだぜ。」って述べています。

 調べたところ、各ブラウザ(Firefox、Opera、Chrome・SafariなどのWebKit系)で結構動作が違っていて興味深かったです。ただ色の変換をしたいだけなのに、わざわざ既存のDOM構造に影響を与えるようなやり方(色を取得したいがために、document.createElementで新たに要素を作成して、それをdocument.body.appendChildしてDOM構造を改変した後で、getComputedStyle()にて色を取得)は、reflowの問題Link もあり出来れば避けたいところ。

 大体以下のような感じでいけそうです。

function ColorToHEX(color){
 var d = document, ecolor, element = d.createElement("button");
 element.style.color = color;
 ecolor = element.style.color; // ポイント!
 if(ecolor.indexOf("#") !== -1) return ecolor; // Operaの可能性が高い
 if(d.uniqueID){ // IE向け
  color = element.createTextRange().queryCommandValue("ForeColor");
  color = ((color & 0x0000ff) << 16) | (color & 0x00ff00) | ((color & 0xff0000) >>> 16);
  return "#" + ("000000" + color.toString(16)).slice(-6);
 } else { // IE以外のブラウザ
  if(/webkit/i.test(navigator.userAgent)){ // Google Chrome, Safariで基本カラーなら
   var basicColors = {
    "aqua" : "#00ffff", "black" : "#000000", "blue" : "#0000ff", "fuchsia" : "#ff00ff", "gray" : "#808080", "green" : "#008000",
    "grey" : "#808080", "lime" : "#00ff00", "maroon" : "#800000", "navy" : "#000080", "olive" : "#808000", "orange" : "#ffa500",
    "purple" : "#800080", "red" : "#ff0000", "silver" : "#c0c0c0", "teal" : "#008080", "white" : "#ffffff", "yellow" : "#ffff00"
   }
   for (var col in basicColors){
    if(color.toLowerCase() === col) return basicColors[col];
   }
  }
  if(ecolor.indexOf("rgb") !== -1){ // RGB形式で色を返した!
   color = ecolor;
  } else color = getComputedStyle(element, null).getPropertyValue("color");
  return "#" + color.replace(/rgb¥((¥d+?),¥s*(¥d+?),¥s*(¥d+?)¥)/g, function(a, r, g, b){
   return ("000000" + ((+r) * 65536 + (+g) * 256 + (+b)).toString(16)).slice(-6);
  });
 }
};
 流れとしてはこうです。
  1. IEでcreateTextRange()を使うのでBUTTON要素を作成。
  2. それに色を指定する。element.style.color = color
  3. 指定した色の取得。この時点で、OperaがHEX形式の値を返してくれる。Opera一抜け。
  4. 同様に、この時点で、IEはqueryCommandValue経由でBGR値を取得できるので、それをHEXに変換。
  5. IE以外でFirefoxは、ecolorは指定したままの値。getComputedStyleを使うとRGB値を返してくれる。
  6. WebKit系は振る舞いがややこしい。Webの基本17色、例えばredの場合、 ecolor = element.style.color にはredがそのまま入り、tomatoなどの拡張色ならRGB形式を返す。じゃ、getComputedStyleはどうかというと、まだDOMに組み入れていないので空の値を返す。
  7. なので、WebKit系で基本17色であれば、それはJSON対応させてHEX値を返すようにします。そうでなければ、RGB値を返してくれるのでHEX値に変換。
  8. 以上。document.body.appendChildなどでのDOM改変は必要ない点がポイントです。

 WebKit系の振る舞いが独特で、特別な処理が必要になりますが、いずれにせよ、DOMの改変を伴わなくて良いかなぁと(まぁcreateElementはしますが)。ちなみに、OperaでもgetComputedStyleは使えますが、FirefoxやWebKitがRGB値を返すのに対して、OperaはHEX値を返します。RGB値からのHEX値への変換は、いくつか方法があるでしょう。上では正規表現を用いています。逆にHEXからRGBへの変換も色々あると思います。

function HEXToRGB(hex){
 return hex.replace(/#?(¥w{2})(¥w{2})(¥w{2})/, function(a, r, g, b){ 
  return "rgb(" + parseInt(r, 16) + ", " + parseInt(g, 16) + ", " + parseInt(b, 16) + ")"}
 );
}

function HEXToRGB(hex){
 var color = parseInt(hex.replace("#", ""), 16);
 return "rgb(" + ((color >> 16) & 0xff) + ", " + ((color >> 8) & 0xff) + ", " + (color & 0xff) + ")";
}

などですかね。

 ついでなので、カラーキーワードからRGB値に変換する関数も挙げておきます。考え方は基本的に同じです。DOMの改変は必要ないです。

function ColorToRGB(color){
 var d = document, ecolor, element = d.createElement("button");
 element.style.color = color;
 element.style.display = "none";
 ecolor = element.style.color;
 if(ecolor.indexOf("rgb") !== -1) return ecolor; // WebKit系が一抜けする可能性あり。
 if(ecolor.indexOf("#") !== -1){ // OperaはHEX値を返す
  return ecolor.replace(/#?(¥w{2})(¥w{2})(¥w{2})/, function(a, r, g, b){ 
   return "rgb(" + parseInt(r, 16) + ", " + parseInt(g, 16) + ", " + parseInt(b, 16) + ")"}
  );
 }
 if(/webkit/i.test(navigator.userAgent)){ // WebKit系で、基本カラーの場合はRGB値を返さない
  var basicColors = {
   "aqua" : "rgb(0, 255, 255)", "black" : "rgb(0, 0, 0)", "blue" : "rgb(0, 0, 255)", "fuchsia" : "rgb(255, 0, 255)",
   "gray" : "rgb(128, 128, 128)", "green" : "rgb(0, 128, 0)", "grey" : "rgb(128, 128, 128)", "lime" : "rgb(0, 255, 0)",
   "maroon" : "rgb(128, 0, 0)", "navy" : "rgb(0, 0, 128)", "olive" : "rgb(128, 128, 0)", "orange" : "rgb(255, 165, 0)",
   "purple" : "rgb(128, 0, 128)", "red" : "rgb(255, 0, 0)", "silver" : "rgb(192, 192, 192)",
   "teal" : "rgb(0, 128, 128)", "white" : "rgb(255, 255, 255)", "yellow" : "rgb(255, 255, 0)"
  }
  for (var col in basicColors){
   if(color.toLowerCase() === col) return basicColors[col];
  }
 }
 if(d.uniqueID){ // IE用
  color = element.createTextRange().queryCommandValue("ForeColor");
  return "rgb(" + (color & 0xff) + ", " + ((color >> 8) & 0xff) + ", " + ((color >> 16) & 0xff) + ")";
 } else { // FirefoxはこれでRGB値を返す
  return getComputedStyle(element, null).getPropertyValue("color");
 }
}

 こんな感じですかねぇ。ファイルサイズが小さくなるなら、cornerplay.jsを書き換えても良いなぁ。

 ちなみに、圧縮したら920バイトになりました。

function ColorToHEX(b){var g=document,a,e=g.createElement("button");e.style.color=b;a=e.style.color;if(a.indexOf("#")!==-1){return a}if(g.uniqueID){b=e.createTextRange().queryCommandValue("ForeColor");b=((b&255)<<16)|(b&65280)|((b&16711680)>>>16);return"#"+("000000"+b.toString(16)).slice(-6)}else{if(/webkit/i.test(navigator.userAgent)){var f={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",grey:"#808080",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",orange:"#ffa500",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00"};for(var c in f){if(b.toLowerCase()===c){return f[c]}}}if(a.indexOf("rgb")!==-1){b=a}else{b=getComputedStyle(e,null).getPropertyValue("color")}return"#"+b.replace(/rgb¥((¥d+?),¥s*(¥d+?),¥s*(¥d+?)¥)/g,function(h,j,i,d){return("000000"+((+j)*65536+(+i)*256+(+d)).toString(16)).slice(-6)})}};

— posted by martin at 03:46 am   commentComment [0]  pingTrackBack [0]

IEでCSS3のドロップシャドウを使いたい

category-icon

 こんばんは、martinです。CSS3+JavsScriptネタでも。目標は、IEでもCSS3のドロップシャドウ効果が得られるようになること。以下のような感じで、Firefoxとほぼ同等の効果を得ることが出来ます。左がIE8で右がFirefoxです。

[2010/08/04 01:02:25] プログラムアップデート→http://p2b.jp/2010/08/04/IE-compatible-CSS3-dropShadowLink

comparison
影のぼやけた感じも再現。
 CSS3では、テキストにドロップシャドウの効果を付けるtext-shadow定義されていてLink (まだドラフト段階かな)、既にFirefox3.5やSafari, Chrome, Opera10などは対応しています。典型的には、以下のような感じで指定します。
text-shadow: 1px 2px 3px tomato; // 順にx-offset、y-offset、ぼかし半径、影の色。最初に色指定でもOK。

 また、テキストに対する影だけでなく、DIVなどのボックス要素に対するドロップシャドウとして、box-shadowLink というのもあり、これは、FirefoxとSafari、Chromeで先行実装されています(Opera10はまだかな)。指定の仕方は、text-shadowと同様です。ほんとは、spread-radiusというオプションのパラメータもあるのですが、これはなくても良いんじゃないかと思ってます。

   -moz-box-shadow: 1px 2px 3px chocolate; // 順にx-offset、y-offset、ぼかし半径、影の色。
-webkit-box-shadow: 1px 2px 3px chocolate; // Safari, Chrome用の記述。

 また、box-shadow固有の属性として、insetというのがあります。これは、ドロップシャドウをボックスの内側に見せるものです。

   -moz-box-shadow: inset 0 0 1em red;
-webkit-box-shadow: inset 0 0 1em red;

 今のところ、insetオプションを理解するのは、Firefox3.5だけのようです。

 あれっ、ドロップシャドウって、IEが10年ぐらい前から実装してなかったっけ?と思う方もいるでしょう。そう確かにそんなものがありました。IE独自のフィルター機能を使ってそれっぽいことが出来ます。

 これらが紹介された当初はクールだと思いましたが、使い勝手が今イチだったのと、あとは、玉に瑕というか白璧の微瑕(はくへきのびか)だったと思うのは(ここまでいうと誉めすぎですが)、影の「ぼかし」が指定出来なかったことです。キリッとした輪郭の影では折角の魅力も半減です(個人的に)。おそらく、影のぼかしはそれなりにCPUのパワーを必要とするので、当時はまだ難しかったのかもしれません。なので、これらが広くWebサイトで使われるということはなかったです。AjaxのようにまたしてもIEの早すぎた実装だったわけです。

 昨今、パソコンの性能も上がり、IE以外のモダンなブラウザがCSS3のtext-shadowをサポートするようになったせいか、この頃は色んなサイトでドロップシャドウ効果を見かけるようになりました。

 なので、まだまだシェアの大きいIEでも、CSS3に準拠した、それらしいドロップシャドウを付けたいということで考えてみました。基本的な考え方は、上で述べたマイクロソフト独自のFilter BlurLink を用いて、影のぼかしを演出するということです。このBlurは、要素自体をぶれた表現にするので、それを別のレイヤーとして生成して、それを影を付けたい要素の背後に配置するというものです。別のレイヤー生成というと、大げさな気もしますが、ぼやけた感じを出すには、これ以外の方法はない気がしますし、Firefoxにしても、影は別レイヤーなんじゃないんでしょうか、知りませんが。

 とりあえず、それっぽいモノができたのでデモをお見せします。

 

IEでもCSS3のドロップシャドウを実現するデモLink

 text-shadowbox-shadowもカンマで区切った複数の指定が可能なので、これにも対応してみました。

.multi-text-shadow {
 font: bold 3em Arial, sans-serif; color: white;
 padding: 10px;
 text-shadow: 1px 2px 2px #555, 0 0 1em blue, 0 0 0.2em blue; /* ここでは3つ指定している */
}
 上の指定だと、以下のような感じになります。
multi-text-shadow
IE8でのスクリーンショットです。

 また、現時点では、Firefox3.5のみが対応しているinsetオプションも解するようにしてみました。IE8でのスクリーンショットをば。

inset-demo
なかなか良い感じでは。
 で、実際の設置方法ですが、なるべく簡単に設置できるように、外部スクリプトとしました。CSSでの記述は、特別なものは必要ないです。普通に上に挙げたような感じで、text-shadowやbox-shadowの指定をしておけば良いです。後は、ドロップシャドウを効かせたいページで、外部スクリプトを次のように呼び出せばOKです。
<script type="text/javascript" src="path/to/css3shadow.htc"></script>
 ファイルの名前css3shadow.htcさえ変えなければOKで、それを置いた相対パスを、各自で記述すれば良いです。

 これ以外の指定方法としては、このスクリプト自体は、完全にIE向けなので、Firefoxその他のブラウザには不要なものです。なので、その分のトラフィックを減らしたいということであれば、IE向けの条件コメントを使って、

<!--[if lt IE9]>
<script type="text/javascript" src="path/to/css3shadow.htc"></script>
<![endif]-->

でも良いと思います。これであれば、Firefoxなどではコメントとして解釈されるので、サーバーからcss3shadow.htcが呼び込まれることはないです。

 まだ、書き上げたばかりなんで、細かいチェックは出来ていませんが、外部スクリプトを呼び込むだけなので、気軽に試して頂けるかと思います。css3shadow.htcを添付しておきます。8KB未満の小さいファイルです。

 ちなみに、影の色指定ですが、透明度を設定できるrgba方式にも対応しています。これは、通常のRGB形式に加えて、4つめに透明度を指定出来るものです。ゼロで透明、1で不透明となります。

rgba(123, 100, 125, 0.3); /* 透明度30%の指定 */

 オフトピですが、RGBって、たまにRBGとタイプミスしたり、あれっ?どっちだっけと混乱するときがありますが、キーボードで、上から順にRGBとなっている、と覚えると良いです。

 以下、Mozillaの参考サイトを挙げておきます。

添付ファイル: css3shadow.htcattachedIcon 

— posted by martin at 12:35 pm   commentComment [14]  pingTrackBack [1]

Google AJAX Feed APIを利用したスライドショー

category-icon

 こんばんは。前回の書き庫コメントでアップロードした画像のスライドショーの話が出ていたので。

 ブログのサイドバーでスライドショーを見せるブログパーツは結構あると思いますが、良く見かけるのはAdobeのFlashを用いて表示させる方法。これは純粋にJavaScriptを使うよりは、凝った演出が出来る傾向にあります。もっとも、最近はモダンなブラウザではCanvas要素Gをサポートしているので、JavaScriptだけでも凝った演出が可能になってますけど(ppBlogでは、写真を回転させたりして表示できますが、この部分でCanvasを使っています)。

 単なる好みの問題ですが、サイドバーでスライドショーを見せる際に演出に凝りまくるのは、あまり好きでありません(特にFlash系)。なので、スライドショーを見せるにしてもJavaScriptのみで動くシンプルなものが良いなぁ、と思いました。スクリプトは自前で用意したいところですが、画像切り替えのアルゴリズムなどを考えるとはまりそうなので、ここは既知のJavaScriptライブラリーを使うことにします。

 で、Google AJAX API Link が良さそうでしたので、これを使ってスライドショーを付けてみました。Googleで、スライドショーそのもののスクリプトを公開しているLink ので、それを利用するだけです。実際のデモは、http://p2b.jp/demo/slideshow.htmlLink  あるいは http://martin.p2b.jp/index.phpLink で確認出来ます(3paneのテーマであれば右側に表示される)で確認できます。

 早速ですが、簡単な設置手順を記しておきます。

  1. mediaRSSファイルが必要なので、それを作成するadmin.phpmodules/mrss2.inc.phpをゲット。
  2. 管理画面の「各種ツール」に「MRSSの作成 2」というメニューがあるので、それを選択。これでmediaRSSが作成される。
  3. 表示させたいテーマのtemplate.phpに必要なJavaScript関連の記述をする。

 基本的にはこれで足ります。template.phpに記述するJavaScriptですが、先に挙げた小生のサイトでは、まず以下のようにして動作に必要なファイルをGoogleのサーバーから外部スクリプトとして呼び込んでいます。

 <script type="text/javascript" src="http://www.google.com/jsapi?key=(AJAX APIのキー)"></script>
 <script src="http://www.google.com/uds/solutions/slideshow/gfslideshow.js" type="text/javascript"></script>

AJAX APIのキー指定ですが、これはなくても動きますが、グーグルのサイトで簡単に入手出来るLink ので手に入れておくと良いでしょう。

 この2つのスクリプトを通じて、予め用意したmediaRSSファイルを読み込んで、画像を順次表示させていく訳ですが、mediaRSSファイルを手書きで用意するのは大変なので、こういうものはppBlogに作らせましょう(手順1, 2)。ppBlogで生成されるmediaRSSは、具体的にはhttp://martin.p2b.jp/feeds/mrss.xmlLink のような感じになります。

 このmediaRSSファイル読み込み時に起動する呼び出し関数は、以下のような記述です。グーグルのサイトにあるサンプルコードとほぼ同じです。

function load(){ // mediaRSSファイルを読み込んだ際のコールバック関数
  var src = "http://martin.p2b.jp/feeds/mrss.xml"; // 読み込むRSSファイルを指定
  var options = {
    displayTime: 3000,  // 一枚あたりの画像表示時間(ミリ秒)
    transistionTime: 1000, // 画像切り替えのフェードアウト・フェードインの時間(ミリ秒)
    scaleImages : true, // 指定したサイズに合わせて画像を縮小させるかどうかのオプション指定
    linkTarget : google.feeds.LINK_TARGET_BLANK // 画像をクリックした際に新しいウィンドウ(タブ)でリンク先を開くか
  };
  new GFslideShow(src, "slideframe", options); // slideframeというID名を持つ要素に対して発動
 }
 google.load("feeds", "1");
 google.setOnLoadCallback(load);

これに対するHTMLの記述は以下のような感じです。これもtemplate.php内の記載になります。

 <div id="slideshow" style="margin: 60px auto 30px auto; width: 120px; height: 150px; background: url(theme/3pane/Images/slide-frame.jpg) no-repeat; padding-top: 30px;">
  <div id="slideframe" style="width: 100px; height: 100px; margin: auto; position: relative;"></div>
 </div><!--#slideshow-->

ここでは、インライン形式のスタイルシート指定にしていますが、勿論スタイルシートのCSSファイルで指定しても構いません。設置の参考にするには、上に挙げたデモサイトが分かりやすいかと思います。

 尚、このスライドショーは、マウスが画像に載ると、そこで一時停止します(これはデフォルトの動作)。で、そのままクリックするとその画像が含まれる記事にリンクするようになっています(これはmediaRSSの内容による)。

 ppBlogに組み込めるように、admin.phpとmodules/mrss2.inc.phpを添付しておきますね。

 参考リンク: http://www.google.com/uds/solutions/slideshow/reference.htmlLink

添付ファイル: admin.phpattachedIcon  mrss2.inc.phpattachedIcon 

— posted by martin at 12:17 am   commentComment [0]  pingTrackBack [0]

VMLやSVGを用いた角丸コーナー

category-icon

 おはようございます。時間があるときに、以下のようなサイトを作成しているのですが、この2枚のスクリーンショットは、違うところがあります。どこが違うのかというと、2枚目のやつは、ボックスのコーナーが丸っこくなっていて、いわゆる角丸コーナーと言われるものになっています。

shot1

shot2
こっちの方がパリっぽい!?

 ちょっとお洒落っぽいサイトデザインにしようと思っているのですが、そのためには、角張ったデザインよりも、角が取れたデザインの方が良いかなと考えました。で、角丸コーナーは、FirefoxやWebKit系のSafariやChromeは、以下のような感じでCSSでの指定が可能です。

-moz-border-radius: 10px; // Firefox
-webkit-border-radius: 10px; // Webkit系

問題は、これらのCSSに対応していないOperaやIEです。Operaは、近い将来にサポートするでしょうけど、IEは、今後数年は期待できないかもです。なので、これら2つのブラウザに対しては、JavaScriptを通じてのアプローチを取ることになります。

 IEでは、VMLを利用するのがよさげです。しかも角丸コーナーのためにあるようなroundrectというのが予め実装されているので、これを使うのが簡単そうですが、例えば最初に挙げたサイトショットにあるように、ナビ―ゲーションタブの上側だけ丸っこくしたいとかいう場合の制御が難しいです。なので、VMLを使うにしても、もうちょっと、凝ってみることにします。そもそも角丸コーナーを実現するには、以下のようなアプローチを取るのが自然でしょう。

principle
この考えでいけば、Firefoxなどのradiusと同じ値を指定できるだろう。

 要は、4つの各コーナーに小さい円を描いて、それをクリッピングすれば良いわけです。これなら、ボーダー半径は、FirefoxやWebkit系で指定する値と同様に振る舞うことが期待出来ます。で、それを実際に書き下したのが以下。

<div style="background:#fffacd; position:absolute; right:-20px; bottom:-20px; width:50px; height:50px; clip:rect(25px 50px 50px 25px);">
 <v:group style="width:50px; height:50px; position:absolute;" coordsize="50,50">
  <v:oval fillcolor="#fff" strokeweight="20px" strokecolor="#ffd700" style="left:9px; top:9px; width:30px; height:30px;" />
 </v:group>
</div>

 これをIEで見ると、以下のように見えます。なかなか良い感じですね。

shot3

 残りの角についても同様にすればOKです。ただし、IEのVMLバグなのかどうか分かりませんが、作成したVML画像の位置が1ピクセルぐらい微妙にずれる場合があるようです。理論的には、OKのはずなんですが。。

 さて、Operaの場合はどうでしょうか。OperaはVMLはサポートしませんが、代わりにSVGをサポートします。なので、これを用いて、上で述べたようなアプローチを取ればOKですね。大体以下のようなスクリプトでOKでしょう。

function svg_corner(id, location, r, obg){
 var d = document, e = d.getElementById(id);
 if(e.currentStyle.position == 'static'){
  e.style.setProperty('position', 'relative', null);
 }
 r = r || 10;
 var svg = d.createElementNS('http://www.w3.org/2000/svg', 'svg');
 svg.style.setProperty('position', 'absolute', null);
 var ibg = e.currentStyle.backgroundColor;
 ibg = ibg == 'transparent' ? '#fff' : ibg;
 if(!obg){
  var p = e.parentNode;
  while(p && p.currentStyle.backgroundColor){
   obg = p.currentStyle.backgroundColor;
   if(p.nodeName == "HTML") break;
   if(obg && obg != "transparent") break;
   p = p.parentNode;
  }
 }
 obg = (!obg || obg == "transparent") ? "#fff" : obg;
 var bc = e.currentStyle.borderColor;
 bc = (!bc || bc == "transparent") ? "#fff" : bc;
 var bw = parseInt(e.currentStyle.borderWidth);
 var rect = d.createElementNS('http://www.w3.org/2000/svg', 'rect');
 var oval = d.createElementNS('http://www.w3.org/2000/svg', 'circle');
 var attr = [];
 switch (location){
  case 'tl' : attr = [r + bw/2, r + bw/2, 'top', -bw, 'left', -bw]; break;
  case 'tr' : attr = [0, r + bw/2, 'top', -bw, 'right', -bw]; break;
  case 'bl' : attr = [r + bw/2, 0, 'bottom', -bw, 'left', -bw]; break;
  case 'br' : attr = [0, 0, 'bottom', -bw, 'right', -bw]; break;
 }
 oval.setAttribute('fill', ibg);
 oval.setAttribute('cx', attr[0]); oval.setAttribute('cy', attr[1]);
 oval.setAttribute('r', r);
 oval.setAttribute('stroke', bc); oval.setAttribute('stroke-width', bw + 'px');
 rect.setAttribute('fill', obg);
 rect.setAttribute('width', r * 2 + bw + 'px'); rect.setAttribute('height', r * 2 + bw + 'px');
 svg.appendChild(rect);
 svg.appendChild(oval);
 svg.style.setProperty(attr[2], attr[3] + 'px', null); svg.style.setProperty(attr[4], attr[5] + 'px', null);
 svg.style.setProperty('width', r + bw/2 + 'px', null); svg.style.setProperty('height', r + bw/2 + 'px', null);
 e.insertBefore(svg, e.firstChild);
}

 これらをまとめると、以下のようなデモンストレーションが出来ます。このデモでは、ID指定した要素に対して角丸を発動していますが、特定のクラス名を持ったものに対して、ということも簡単に出来るでしょう。

どのブラウザでもほぼ見た目が同じな角丸コーナーのデモLink

 上のサンプルでは、ついでにFirefoxやWebkit系でも、スクリプトでボーダー属性を指定するようにしています。このふたつ、指定の仕方が微妙に違うんですよねぇ。ちょっとはまりました。以下の感じ。
document.getElementById(id).style.WebkitBorderTopLeftRadius = radius + "px"; // Webkit系
document.getElementById(id).style.MozBorderRadiusTopleft = radius + "px"; // Firefox。leftのLも小文字。

 なお、OperaでのSVGの使い方は、「Operaで丸角を実現するCSSを試してみた (解決)Link 」や「角丸にするためにライブラリを作ってみるLink 」が参考になりました。

— posted by martin at 01:15 pm   commentComment [0]  pingTrackBack [0]

正規表現を使わないCSS3対応の「属性限定」セレクタ関数

category-icon

 ppBlogでは、不十分ですけどCSS3対応のセレクター関数を実装していたんですが、これを大幅に書き換えました。従来は、XPathが使えるブラウザであれば、CSS3のセレクタをXPath用に変換して、意中の要素を取り出したりしていました(スピード面でなかり有利)。でも、document.querySelectorAllをサポートするブラウザが当たり前になってきたので、少なくともppBlogでは、XPathは不要という結論に達しました(今やFirefoxのためだけに用意してる感じで、しかも次期v3.5では間違いなくquerySelectorAllがサポートされるでしょうから)。

 ppBlogで使っている要素抽出のためのセレクターは、大体以下のような感じです。

  • o("#ID") 系 // IDを持つ要素を取り出す
  • o(".className") 系 // 特定のクラス名を持つ要素を取り出す
  • o("a[rel=edit]") // 属性セレクター系。A要素でrel="edit"のやつを取り出す
  • o("textarea[id^=Page]") // 属性セレクター系。テキストエリア要素でID名がPageで始まる要素を取り出す

とまぁ、こんな感じで、ほぼ頻度順になってて、圧倒的にID名抽出が多いです。なので、別に高機能なセレクター関数は必要ないわけで。XPathへの変換にえらくファイル容量を割いていたんですが、それをばっさり削ったんで、だいぶシェイプアップされました(oParts.js)。

 ただ、属性セレクター(上のリストの下2つ)は、今後、色々使い道がありそうなので、これはスピードも考慮した関数を実装しました。以下のような関数です。正規表現を使っていないので、速度面で有利です。面白いことに、FirefoxよりIE6-8の方が高速に動きます。別に詳しく計測した訳ではありませんが、属性セレクターに限定すれば最速の部類ではないかと思っています(XPathやquerySelectorAllを使わないレガシーな系で)。

// cache = {}; // キャッシュ使うなら
function queryByAttributeSelector(rule){
 var apos = rule.indexOf('[');
 var tag = rule.slice(0, apos); // タグ名を抽出。
 var attr = rule.slice(apos + 1, -1); // 属性を抽出
 attr = (attr.indexOf('][') > 0) ? attr.split('][') : [attr];
 var s = '', e, k, ks, v, z, i = 0, l = attr.length;
 // if(!cache[rule]){ // キャッシュ使うなら
 for(; a = attr[i++];){
  e = (i != l ? ' && ' : '');
  z = a.split('=');
  if(z[1]){
   k = z[0], ks = k.slice(-1);
   v = z[1].indexOf('"')===0 ? z[1].slice(1, -1) : z[1];
   k = k.indexOf('class') !== -1 ? k.replace('class', 'className') : k;
   switch (ks){
    case '^' : k = k.slice(0, -1); s += 'n["'+k+'"].indexOf("'+v+'")===0' + e; break;
    case '$' : k = k.slice(0, -1); s += 'n["'+k+'"].lastIndexOf("'+v+'")===(n["'+k+'"].length - "'+v+'".length)' + e; break;
    case '~' : k = k.slice(0, -1); s += '(n["'+k+'"]=="'+v+'" || n["'+k+'"].indexOf("'+v+' ")===0 || n["'+k+'"].indexOf(" '+v+' ")!==-1 || ((z=n["'+k+'"].indexOf(" '+v+'"))>0 && !n["'+k+'"].charAt(z+"'+v+'".length+1)))' + e; break;
    case '*' : k = k.slice(0, -1); s += 'n["'+k+'"] && n["'+k+'"].indexOf("'+v+'")!=-1' + e; break;
    case '|' : k = k.slice(0, -1); s += '(n["'+k+'"]=="'+v+'" || n["'+k+'"].indexOf("'+v+'-")!= -1)' + e; break;
    default  : s += 'n["'+k+'"]=="'+ v +'"' + e;
   }
  } else {
   s += 'n["'+(a==='class'?'className':a)+'"]' + e;
  }
 }
 /*cache[rule] = s;   // キャッシュ使うなら
 } else {
  s = cache[rule];
 }*/
 var r = [];
 var items = document.getElementsByTagName(tag||'*'), j = 0, n;
 var v = new Function('n', 'return ('+s+')');
 for (;n = items[j++];){
  if(v(n)){
   r[r.length] = n;
  }
 }
 return r;
}

 工夫した点は、indexOfやらlastIndexOfの積極的活用です。split関数は、正規表現を用いない文字列での分割は、用いる場合より何倍も高速です。後、細かい速度の改善については、uupaaさんLinkエントリLink [latest log]が非常に参考になりました。

CSS3の「属性」セレクターをほぼ網羅していると思いますが、とりあえずhttp://mootools.net/slickspeed/Link にある属性セレクターはすべてパスします(ただし、div[class!=made_up]は、CSS3にはなさそうなので未対応)。

 上のソースコメントをみれば分かりますが、キャッシュを有効にするとより速くなります。でも別になくても良いかなと思ってます。細かい点を言えば、A要素のREL属性なんてのは、大文字小文字を区別しないようですが、そこまでは追ってません。要は、ppBlogで動けば良いので。

 ppBlog1.8.0での実装は、上のソースをベースにしたものになっています。

— posted by martin at 09:11 pm   commentComment [0]  pingTrackBack [0]

T: Y: ALL: Online:
Created in 0.0064 sec.
prev
2024.3
next
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31