GLOBAL-ALIGN::LEFT CHAPTER::はじめに 拡張機能 勉強会2 ---- 自己 紹介 ---- Piro ---- 作ってる 拡張機能 ---- ・タブブラウザ拡張(Fx2非対応) ・コンテキストメニュー拡張(Fx2非対応) ・コンテントホルダー(Fx2非対応) ・Ez Sidebar(Fx2非対応) ・ポリシーマネージャ(Fx2非対応) ・タブキラー(Fx2非対応) ---- (´Д`;) ---- ・ALTのポップアップ表示 ・XHTMLルビサポート ・テキストリンク ・タブカタログ ・2ペインブックマーク ・巻き戻し/早送りボタン ・セカンドサーチ ・分割ブラウザ ・XUL/Migemo【Forked Edition】 ---- 今日の ネタ ---- Canvas ---- [[PRE: ]] ---- って 何? ---- スクリプトから 操作可能な 自由描画領域 ---- 線とか 塗りとか 文字とか ---- SVG との違い ---- DOM-less ---- オブジェクト的な 操作はできない (描き捨て) ---- 仕様 ---- WHATWG (W3Cではない) ---- Web Applications 1.0 Working Draft [[3.14.7. The canvas element|http://www.whatwg.org/specs/web-apps/current-work/#the-canvas]] ---- XHTMLの 名前空間に 属する ---- 名前空間URIぐらい 変えるだろ、 常識的に考えて……  /   _ノ  \  |    ( ●)(●) . |     (__人__)   |     ` ⌒´ノ ---- CHAPTER::基本的な使い方 基本的な 使い方 ---- 1. 生成 2. コンテキストを取得 3. 描画 4. (゚д゚)ウマー ---- 普通に書く場合 [[PRE: ]] ---- DOMで動的に生成 [[PRE: const XHTMLNS = 'http://...'; var canvas = document.createElementNS( XHTMLNS, 'canvas'); canvas.setAttribute('width', 300); canvas.setAttribute('height', 200); var box = document.getElementById('box'); box.appendChild(canvas); ]] ---- サイズを指定しなかったら 横300ピクセル  ×縦150ピクセル になります。(豆知識) ---- コンテキストを取得 [[PRE: var canvas = document .getElementById('canvas'); var context = canvas .getContext('2d'); ]] ---- 描画 [[PRE: var x = 30; var y = 30; var width = 100; var height = 50; // CSSの指定と同じ書き方 var color = 'rgb(255, 128, 64)'; // これから先の塗り潰し操作はこの色でやってNE! context.fillStyle = color; // この位置を塗りつぶしてNE! context.fillRect(x, y, width, height); ]] ---- 描画の原点や角度の変更 [[PRE: // 原点を現在の原点からx, y分移動する context.translate(x, y); // 描画の倍率を0.5倍にする context.scale(0.5, 0.5); // 描画の角度を30度回転する context.rotate(30 * Math.PI / 180); ]] ---- 新しく描く図形の 位置・大きさ・角度が 変わるだけ ---- 現在までに描画された内容を 回転させたり移動させたり することはできない (画像やcanvasの内容を  canvasに描画する  機能を使えば可能?) ---- フレームの内容の描画 [[PRE: var w = window; context.drawWindow(w, w.scrollX, w.scrollY, w.innerWidth, w.innerHeight, 'rgb(255,255,255)'); ]] ---- canvasの大きさに合わせて縮小する場合 [[PRE: context.scale(canvas.width/w.innerWidth, canvas.height/w.innerHeight); context.drawWindow(w, w.scrollX, w.scrollY, w.innerWidth, w.innerHeight, 'rgb(255,255,255)'); ]] ---- こんな感じ です。 ---- 詳しくは [[MDCのCanvas関係のページ|http://developer.mozilla.org/ja/docs/Category:HTML:Canvas]] をごらんあれ ---- CHAPTER::できること、できないこと Canvasで できること できないこと ---- できること ---- ・パスのストローク描画・塗り潰し ・半透明/透過 ・グラデーション ・描画結果をPNG/JPEG出力  (Fx2) ・ビットマップ画像の埋め込み ・クリッピング ・拡大/縮小 ・フレームの内容を描画 ・変換行列による変形(Trunk) ---- できないこと ---- ・文字列の描画 ・描画結果をDOMで操作 ・CSSで描画を制御 ---- SVGほどの パワフルさは 必要ない という場面向け ---- CHAPTER::Canvasの注意点 注意点 ---- toDataUR[[EM:I]] じゃないよ toDataUR[[EM:L]] だよ ---- toDataURLは [[EM:canvasのメソッド]] だよ コンテキストの メソッドじゃないよ ---- widthとheightは [[EM:CSSでだけ指定 しても駄目だよ]] ---- CSSでの指定だけだと、 canvasの [[EM:描画領域のピクセル数]] は変わらず[[EM:表示サイズ]] だけが変わる ---- 駄目な例 [[PRE: var canvas = document.createElementNS( XHTMLNS, 'canvas'); canvas.style.width = '600px'; canvas.style.height = '300px'; ]] →300×150(デフォルトの大きさ)の  キャンバスが2倍の大きさに拡大して  表示されてしまう ---- 良い例 [[PRE: var canvas = document.createElementNS( XHTMLNS, 'canvas'); canvas.width = 600; // setAttributeも可 canvas.height = 300; // setAttributeも可 canvas.style.width = '600px'; canvas.style.height = '300px'; ]] ---- 他のウィンドウの 内容を描画するには [[EM:UniversalBrowserRead権限]] が必要 ---- drawWindowを実行する前に 権限の取得をお忘れなく [[PRE: netscape.security.PrivilegeManager .enablePrivilege('UniversalBrowserRead'); ]] ※拡張機能の場合は最初から  権限があるので不要 ---- drawWindowを 何度も使う場合 ---- toDataURLで画像に 変換してキャッシュ させた方が高速に なるよ ---- 利用例:[[Tab Effect XP|https://piro.sakura.ne.jp/latest/blosxom/mozilla/extension/2007-02-02_tabeffectxp.htm]] ---- CHAPTER::おまけ:要素の自由な配置 おまけ ---- 要素の 自由な配置 ---- position:fixed を指定すると XUL要素を画面上の 好きな位置に 表示できる ---- [[PRE: box#someID { position : fixed; top : 0; left : 0; background : url(transparent-image.png); width : 1024px; height : 768px; } ]] ---- [[日記で詳しく解説してます|https://piro.sakura.ne.jp/latest/blosxom/mozilla/xul/2007-02-02_splitbrowser-popupbuttons.htm]] ---- canvasと 組み合わせると 表現の幅が 広がる ---- 注意点 ---- たまに クラッシュ する ---- style属性で position:fixedを指定とか style.position = 'fixed'とか HTMLの名前空間の要素に 指定とか ---- 詳しい条件が 分かり次第 日記あたりで まとめます ---- CHAPTER::タブカタログでの利用例 話を 戻す ---- タブカタログ ---- canvasの 利用例 ---- 大まかな 構造 ---- [[PRE: ... ... ←背景バッファ ←サムネイル ←サムネイル ... ]] ---- タブの サムネイル ---- 基本的には さっきの 例の通り ---- 工夫1 ---- canvas要素を タブごとに キャッシュ ---- 2度目以降の 表示を 若干高速化 ---- 工夫2 ---- ドロップ シャドウ ---- サムネイルの 周囲の影 ---- tableでの 角丸の応用 ---- XBLと grid ---- [[PRE: ]] ---- [[PRE: ]] ---- 上下左右の4辺 +4つの角 =8種類の画像 ---- 工夫3 ---- position:fixed ではなく position:absolute で配置 ---- 何故? ---- スクロール のため ---- 全部のサムネイルが position:fixedで 配置されている場合 ---- [[PRE: ... ]] ---- 表示位置を変えるには 全てのサムネイルの top/leftを 変更しないといけない ---- めんどい ---- そこで ---- 親要素を一つ設けて position:fixedで配置し その中に position:absoluteで サムネイルを配置 した場合 ---- [[PRE: ... ]] ---- ALIGN::center position:absoluteな ボックスの配置の原点 || [position:fixedな親ボックスの位置] ---- つまり 親ボックスの top/leftをいじれば ---- 中にある全ての サムネイルが 連動して動く ---- 楽チン ---- 背景の バッファ ---- なぜ 必要? ---- 透過処理の バグ(?)回避 のため ---- インライン フレームの上に 半透明の要素や キャンバスを置くと ---- 透明になるはずの部分が 背景色になってしまう ---- 実例 ---- 下にcanvasを置いて 描画バッファにする ---- Fx2のフィッシング 詐欺警告などでも 同じテクニックが 使われている ---- 欠点 ---- バッファに描画した フレームには スクロールバーが 描画されない ---- 実例 ---- ポイント ---- 個々の サムネイルに canvasを用意 ---- 何故? ---- 一枚のcanvasに 背景もサムネイルも 全部描画しちゃ ダメなの? ---- 理由 ---- canvasは DOM-lessだから ---- ・個々のサムネイル上での  クリック操作の検出 ・ドラッグ操作の検出 ができない ---- 個々のサムネイルの 表示スタイルを CSSに分離できない ---- ポリシー ---- 開発効率 > 速度 ---- 富豪 的 ---- 楽に開発できる方が 他にも色々手を出せて 楽しいよね ---- CHAPTER::その他の工夫 その他の 工夫(?) ---- 工夫1 ---- なるべくサムネイルが 大きく表示される ようにするには どうすればいい? ---- サムネイルの 大きさを算出 するメソッド ---- [[PRE: calculateThumbnailSize : function(aRelative) { // aRelativeはズームの指定 var w = window.innerWidth; var h = window.innerHeight; var padding = this.padding; var header = this.header; var tabNum = this.tabs.length; var boxObject = gBrowser.getBrowserForTab(gBrowser.selectedTab).boxObject; var aspectRatio = boxObject.height / boxObject.width; var minSize = this.getPref('extensions.tabcatalog.thumbnail.min.enabled') ? Math.min(this.getPref('extensions.tabcatalog.thumbnail.min.size'), (aspectRatio < 1 ? boxObject.width : boxObject.height )) : -1; var maxCol, overflow = false; ]] ---- ウィンドウの面積を元にして 最大の大きさをざっくり求める [[PRE: var thumbnailMaxSize = w * h * 0.8 / tabNum; var boxWidth = parseInt(Math.min( Math.sqrt(thumbnailMaxSize), window.outerWidth * 0.4 )) - 4 - padding; var boxHeight = parseInt(boxWidth * aspectRatio); ]] ---- 先に、最低サイズが決まっている場合の例外処理をこなしておく [[PRE: if (aRelative !== void(0) || minSize > 0) { // ズームイン if (aRelative > 0) { boxWidth = parseInt(Math.min(this.catalog.tnWidth * 1.2, boxObject.width)); boxHeight = parseInt(boxWidth * aspectRatio ); } // ズームアウト else if (aRelative < 0) { boxWidth = parseInt(Math.max(this.catalog.tnWidth * 0.8, header)); boxHeight = parseInt(boxWidth * aspectRatio ); } else { // サムネイルの最小サイズが設定されている場合 if (aspectRatio < 1) { if (boxWidth <= minSize) { boxWidth = minSize; boxHeight = parseInt(boxWidth * aspectRatio ); } } else if (boxHeight <= minSize) { boxHeight = minSize; boxWidth = parseInt(boxHeight / aspectRatio ); } } maxCol = Math.max(1, Math.floor(w / (boxWidth + padding))); overflow = ((boxHeight + padding + header) * Math.ceil(tabNum / maxCol)) > h ; } ]] ---- 全てのサムネイルが画面内に収まるように大きさを自動調整 [[PRE: else { var boxWidthBackup, maxRow; do { // 今のサムネイル幅で最大の列数を求めておく maxCol = Math.ceil(Math.sqrt(tabNum)); while (maxCol > 1 && (boxWidth * maxCol) > w) { maxCol--; } boxWidthBackup = boxWidth; // 全てのサムネイルが縦に収るようになるまで縮小する maxRow = Math.ceil(tabNum/maxCol); while ((boxHeight * maxRow) > h) { boxWidth = parseInt(boxWidth * 0.9); boxHeight = parseInt(boxWidth * aspectRatio ); } } while (boxWidthBackup != boxWidth); // もしサムネイル幅が小さくなっていたら、 // 横に余裕が生まれている可能性があるので計算をやり直す } ]] ---- ここまでで求めた情報全てを返却する [[PRE: return { width : boxWidth, height : boxHeight, maxCol : maxCol, maxRow : Math.ceil(tabNum / maxCol), overflow : overflow }; }, ]] この情報を元にサムネイル一覧を描画 ---- 工夫2 ---- ↑↓←→キーと テンキーで 8方向に自由に フォーカス移動 ---- TabCatalog .moveFocus(aDX, aDY) ---- [[PRE: moveFocus : function(aX, aY) { var focusedNode = this.getFocusedItem(); if (!focusedNode) return; var maxX = this.catalog.maxX; var maxY = this.catalog.maxY; var x = parseInt(focusedNode.getAttribute('x'))-1; var y = parseInt(focusedNode.getAttribute('y'))-1; ]] ---- [[PRE: do { if (aX == 0) { } else if (aX < 0) x = (x - 1 + maxX) % maxX; else x = (x + 1) % maxX; if (aY == 0) { } else if (aY < 0) y = (y - 1 + maxY) % maxY; else y = (y + 1) % maxY; focusedNode = this.catalog.parentNode .getElementsByAttribute( 'thumbnail-position', (x+1)+'/'+(y+1) ); } while (focusedNode.length == 0); this.moveFocusToItem(focusedNode[0]); }, ]] ---- ・サムネイル生成時に  X方向の最大の個数(列数)と  Y方向の最大の個数(行数)を  カウントしておく ・個々のサムネイルに  thumbnail-position="1/3"  という形でX, Yの座標を持たせておく ・それらを手がかりにノードを検索 ---- 工夫3 ---- ドラッグ操作を 活用できるようにする ---- ドラッグで選択 →リリースで処理を実行  またはドラッグ中に実行 ---- 例 ---- ・Adobe Photoshop ・Adobe Illustrator ・iRider        など ---- どんな 場合に 便利? ---- ・多数のアイテムがある ・同じ操作を複数のアイテムに  対して実行したい ・片手がふさがっている  (Shift-クリックなどが   できない状況) ---- 拡張機能に移植 ・タブブラウザ拡張  (タブのクローズ) ---- もう ひとひねり ---- リリース時点で メニューを出す →色々な操作ができる ---- 例 ---- ・Konqueror(KDE) ・Windowsでのファイルの  右ドラッグ ---- 拡張機能に移植 ・タブカタログ  (タブの選択と   クローズ)