はてなブログの見た目を綺麗にした話
僕はフロントエンドエンジニアと名乗ったりUI/UXを構築しているとかWebの画面を作っているなどと自己紹介する人間なので、もちろんその当人のブログは綺麗で使いやすくなければいけないと数年間思っていました。
というわけで今回ははてなブログの見た目を綺麗にするにあたって苦労した点と楽勝だった点をお送りします。
綺麗にする前と後の比較
PC版はもともと綺麗な気がしたので、主にレスポンシブデザインによるスマートフォン表示の改善を頑張りました。
レスポンシブデザイン
開閉式のサイドバー
はてブコメント欄
メニューボタンで開閉するメニューは15分で仕上がってしまい拍子抜けでしたが、ブコメ欄の改修はiframe罰と非同期罰を食らったため2時間分の歯ごたえがありました。
カスタマイズの手順
なお、これからソースコードを交えた解説を行いますがあくまで僕の環境においての検証しか行なっておりませんのでもし皆様のブログに適用する際にはこの記事の内容をよしなに読み替えてください。
ソースコードと解説
開閉式のサイドバー
まずはソースをまるごと。これをヘッダのカスタムHTML記述欄に書きます。
<!DOCTYPE html> <!-- スマホ版の追従メニュー --> <input class="sp-menu-toggle" id="sp-menu-toggle" type="checkbox"> <label class="sp-menu-toggle" for="sp-menu-toggle">menu</label> <div class="sp-menu-wrapper" id="sp-menu-wrapper"></div> <style> .sp-menu-toggle { display: none; } @media screen and (max-width: 960px) { label.sp-menu-toggle { display: block; position: fixed; right: 12px; bottom: 36px; color: #fff; background: #ea80a8; padding: 4px 12px; border-radius: 9px; opacity: 0.75; z-index: 9999; } .sp-menu-wrapper { box-sizing: border-box; position: fixed; top: 0; right: -240px; width: 240px; padding: 12px; background: #fff; height: 100%; overflow: scroll; -webkit-overflow-scrolling: touch; transition: 0.3s; box-shadow: 0 0 12px 4px rgba(127, 127, 127, 0.3); visibility: hidden; z-index: 100; } input.sp-menu-toggle:checked ~ .sp-menu-wrapper { right: 0; visibility: visible; } } </style> <script type="text/javascript"> ;(function(window) { window.onload = replaceSidebar window.onresize = replaceSidebar function replaceSidebar() { var wrapper = document.getElementById('sp-menu-wrapper'); var sidebar = document.getElementById('box2'); var menu = document.getElementById('box2-inner'); if (window.innerWidth < 960) { wrapper.appendChild(menu); } else { sidebar.appendChild(menu); } } })(window); </script>
こちらの構成としては
- 開閉メニュー用のマークアップ
- 開閉メニューのスタイル定義
- サイドバーのDOMを開閉メニュー内に移設するスクリプト
となっております。なかでも見所はこちらのCSS。
input.sp-menu-toggle:checked ~ .sp-menu-wrapper { right: 0; visibility: visible; }
皆さんこの間接セレクタfoo ~ bar
をお使いでしょうか?直後の要素を指定するfoo + bar
や1階層直下を指定するfoo > bar
は見かけますかね。間接セレクタを使うと同じDOMツリー内の後続要素を指定することができます。上記CSSはチェックされたinput要素に後続する.sp-menu-wrapper
を指定しています。
対応するマークアップはこちらです。
<input class="sp-menu-toggle" id="sp-menu-toggle" type="checkbox"> <label class="sp-menu-toggle" for="sp-menu-toggle">menu</label> <div class="sp-menu-wrapper" id="sp-menu-wrapper"></div>
先述のCSSと組み合わせることによりチェックが入っているか否かをフラグにJSで状態を保持することなくサイドバーの開閉を実現できます。
ブコメ欄へのスタイル反映
こちらはフッタに記述しています。まずは全体のソース
<!DOCTYPE html> <!-- ブコメ欄のスタイル上書き --> <style id="js-bukome-style"> #hatena-bookmark-container { background: #fffee4 !important; border: none !important; overflow: scroll !important; } #hatena-bookmark-container a { color: #8a5c2e !important; } #hatena-bookmark-container .hatena-bookmark-title a, #hatena-bookmark-container .hatena-bookmark-addcomment a { color: #fff !important; } #hatena-bookmark-container .hatena-bookmark-footer * { color: #ea80a8 !important; } .hatena-bookmark-title { background: #4db399 !important; border: none !important; } .hatena-bookmark-addcomment { background: #ea80a8 !important; border-radius: 9px !important; border: none !important; } .hatena-bookmark-addcomment a { border: none !important; } .hatena-bookmark-item { font-size: 75% !important; } .hatena-bookmark-footer * { margin-top: 0.3em !important; } .hatena-bookmark-showall-comment { float: none !important; text-align: right !important; } </style> <script type="text/javascript"> ;(function(interval) { var iframes = document.getElementsByClassName('hatena-bookmark-comment-iframe'); (function loop() { if (Array.apply(null, iframes).reduce(overwrite, false)) { setTimeout(loop, interval); } })(); function overwrite(failure, iframe) { if (failure) { return true; } var container = iframe.contentDocument.getElementById('hatena-bookmark-container'); if (!failure && container) { var style = document.getElementById('js-bukome-style'); iframe.contentDocument.head.appendChild(style.cloneNode(true)); iframe.contentDocument.body.style.fontFamily = "Helvetica, Arial, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', メイリオ, Meiryo, sans-serif" console.log('ブコメCSSの上書きに成功'); return false; } console.log('ブコメCSSの上書きを再試行'); return true; } })(640); </script>
最初は普通にCSSを定義すればブコメ欄にも適用されると考えていたんですが、どうもブログテーマのCSSカスタマイズからではブコメ欄のスタイルを変更できない。何故だろうとHTMLをよく見てみると、ブコメ欄はiframeによる独立したページな上に非同期でコンテンツが読み込まれていたのです。これは大変でした。
非同期読み込みの完了イベントをlistenしようと試みましたがイベントが発火しないため断念し、結局ブコメ欄にスタイルが当たるまで処理の再試行を繰り返すコードとなりました。
;(function(interval) { // styleタグへの参照を取得 var style = document.getElementById('js-bukome-style'); // 全てのブコメ欄iframeへの参照を取得。 var iframes = document.getElementsByClassName('hatena-bookmark-comment-iframe'); // 再帰呼び出し可能な関数を定義 (function loop() { // "Array like object"をArrayに変換しreduceメソッドにoverwrite関数と失敗フラグ初期値のfalseをセット if (Array.apply(null, iframes).reduce(overwrite, false)) { setTimeout(loop, interval); // 失敗フラグがtrueを返した場合この関数を指定時間後に再帰呼び出し } })(); // 失敗フラグと個別のiframeを受け取り上書き処理を行う関数 function overwrite(failure, iframe) { // 失敗フラグがtrueもしくは"#hatena-bookmark-container"要素が存在しない場合 if (failure || !iframe.contentDocument.getElementById('hatena-bookmark-container')) { console.log('ブコメCSSの上書きを再試行'); return true; // 失敗フラグを返す } // styleタグを複製してiframe内のheadタグに挿入 iframe.contentDocument.head.appendChild(style.cloneNode(true)); // 何故かこれが必要なのでfont-familyを指定 iframe.contentDocument.body.style.fontFamily = "Helvetica, Arial, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', メイリオ, Meiryo, sans-serif" console.log('ブコメCSSの上書きに成功'); return false; } })(640);
以下、手順をおさらいです。
- id指定でスタイルタグ(
<style id="js-bukome-style">
)への参照を取得。 - ページ内に複数あるブコメ欄の
iframe
への参照をclass名指定で全て取得。 iframe
への参照を格納したオブジェクトをreduce
メソッドの使用可能なArray
に変換。- スタイル注入失敗時に
true
を返すreduce
メソッドの初期値として第二引数にfalseを指定。 - 上書きに失敗すると
true
を返すoverwrite
関数を配列の各要素に適用。 reduce
メソッドがtrue
を返した場合、指定した間隔を置いてloop
関数を再帰呼び出し。- 全てのiframeに対してスタイル注入が成功するまで手順3-6の繰り返し。
感想
0からブログを作るよりは楽かなと思いました。
TypeScript入門者がハマりがちな記法(文法)の呼び方と簡易解説まとめ
この記事は、かつてTypeScriptで見かける記法が何と呼ぶのかすら分からず困っていた頃の自分へ向けて送るものですが、現在TypeScriptへ入門中の方々にも役立つかなと思い公開する次第です。正確な内容に関してはぜひ公式をご覧ください。
内容の修正や項目の追加などは随時承ります。
Type Annotation
こんな奴
let foo: number foo = 777 // OK foo = 'hoge' // コンパイルエラー
日本語での呼び名
- 型注釈
- 型アノテーション
ふわっと紹介
値を初めて定義する際によく使われます。
値に初期値が存在する場合は型推論が働き、let foo = 777
のような宣言でもfoo
の型がnumber型
であることをコンパイラが推定してくれます。初期値の無い宣言で使われることがほとんどですが、代入時の推論には無理がある場合などでもちょこちょこ使います。
参考文献
Type Assertion
こんな奴
const foo = <Bar> bar // <Type> value const foo = bar as Bar // value as Type
<Type> value
もしくはvalue as Type
という、式として等価な2通りの記法が存在します。- 可能ならば
value as Type
での記述が推奨されます <Type> Value
での記述はJSX構文と衝突するためTSXファイルで使用できません
日本語での呼び名
- 型アサーション
ふわっと紹介
型アサーション
は値の持つ型情報を実行時の値とは別に上書きします。
実際の値の型とアサーションの型が確実に一致するとき以外は使用すべきでありません。
コード付き解説
const element: HTMLElement // 代入される型を**無視して**`bar`の型が`number型`_となります。 const foo = inputElement as HTMLInputElement const foo = bar.baz() as string // 2.式の型情報を上書き const foo = (bar as string).toLowerCase() // 3.括弧によるアサーションの評価順の変更
上記例では、bar
の#baz()
メソッドが返す値とは別に、foo
はstring型
と見做されます。
また、型アサーションは式であるため、括弧によって評価順を変更することも可能です。
例では、bar
の型情報をstring型
に強制しているため、Stringオブジェクトの持つ#toLowerCase()
メソッドが使用可能です。
ここから少し深入りします。
const num = 777 num.toUpperCase() // コンパイルエラー。
#toUpperCase()
メソッドはnumber型
に存在しない、Stringオブジェクトのメソッドであるためコンパイルエラーとなります。というわけで型アサーションを書き加えますが、
const num = 777 as string // コンパイルエラー num.toUpperCase()
このような、明らかに間違った型によるアサーションもコンパイルで弾かれます。
const num = 777 as any num.toUpperCase() // コンパイルは成功するものの実行時にエラーが投げられる
まずas any
によりnum
の型はany型
へと上書きされnumber型
ではなくなります。
any型
は任意の型として扱えるので Stringオブジェクトのメソッドである#toUpperCase()
を呼び出すコードがコンパイルでは通りますがしかし、実行時にはエラーを吐きます。any型
の扱いには気をつけましょう。
Generics
こんな奴
Foo<T>
new Foo<Bar>()
foo<Bar>()
日本語での呼び名
- ジェネリック
- ジェネリクス
- (特に
を指して)ジェネリック型
ふわっと紹介
例えばArray<T>
という型は「任意で特定の型としてTを内包するArray」という意味になります。
Array型
はそれ自体の定義時にどのような型を内包するのかは不明ですが、実際に使用される際にはインスタンスの内包する型が定まっています。例えばArray<number>
という表記はnumber型
のみで構成されたArrayであるという型を意味します。
つまり、クラスやインターフェイスの宣言時には特定できない、ある時点で判明する型を別途指定するための仕組みがジェネリクスです。
※先述のType AssertionとGenericsとは似て非なる別物です。ご注意ください。
コード付き解説
Array<T>
やReact.Component<P, S>
などという型として用いられている場合が最初に遭遇するケースとして多いかと思います。
functionでの利用例
// この時点でTは任意の型となり得る。 function toArray<T>(...args: T[]): T[] { return args } // "words"の型は引数の型から推論され、string[](== Array<string>)となる。 const words = toArray('foo', 'bar', 'baz') words.push('hoge') // OK words.push(1) // コンパイルエラー
クラスでの利用例
class MyObject<T> { members: T[] constructor(...members: T[]) { this.members = members } pick(index: number): T { return this.members[index] } append(member: T): void { this.members.push(member) } } // コンストラクタからの型推論によりpick()メソッドの戻り値はstring型となるため、以下のようなチェーンも可能。 const withString = new MyObject('foo', 'bar', 'baz') const str = withString.pick(1).toUpperCase() // 'BAR' // 以下の記法は"generic parameter"とも呼ばれ、コンストラクタでは型が指定できない場合などに用いる。 const withNumber = new MyObject<number>() withNumber.append('foo') // コンパイルエラー
React Component定義での利用例
import React from 'react' export interface MyProps { hoge: string } export interface MyState { fuga: string } export default class MyComponent extends React.Component<MyProps, MyState> { /* 任意の実装 */ }
Reactが提供するComponentクラスの場合は継承時にPropsとStateの型が定まるため、実際に使用する際には以下のようなコードになります。
参考文献
- https://www.typescriptlang.org/docs/handbook/generics.html - TypeScript公式
- http://ufcpp.net/study/csharp/sp2_generics.html - C#による解説1
以上、2項目でした。
項目は思いつきor要望があり次第、随時追加予定です。
-
C#の主要開発者であるMicrosoft社はTypeScriptの生みの親↩
新しいブログを作りました。
ここの紹介です。
このブログにはプログラミングにまつわる内容の記事を投稿していきます。
今回は何故か放置せず更新頻度を保てそうな予感がします。
1発目に挨拶もなく普通の記事を投稿してしまい開設の挨拶は2番目となりましたが、どうぞよろしくお付き合いください。
「プログラムから使わないけど人間には役立つ資料群」の最適な保存先
結論
関連ファイルはすべて保存しよう!
説明
手順
- 保存先ディレクトリ名を決定 (今回は仮に
_MyDocs
とする) ~/.gitignore_global
に_MyDocs/
の指定を追加_MyDocs
をプロジェクトルートに作成- プロジェクト本体から独立したGitリポジトリで
_MyDocs
以下を管理 - 少しでも関連しそうなファイルは全てここにも保存
やったー!これで二度と資料を失くさずにすみますね!
~/.gitignore_global
のおかげでプロジェクト本体リポジトリへの間違いコミットも起きません。
余談
オススメ運用方針
- 自分専用の領域として絶対に誰にも公開せず誰とも共有しない
- 好き勝手にしていい適当で気軽な領域として扱う
上記2点を意識するだけで「資料置き場」としての価値が更に高まることと思われます。
以上、知ってる人には今更な、ドキュメント管理で迷わないため僕が実践してるアイディアについてでした。