ぐっちょむの開発にっき

プログラミングの話をするブログ

はてなブログの見た目を綺麗にした話

僕はフロントエンドエンジニアと名乗ったりUI/UXを構築しているとかWebの画面を作っているなどと自己紹介する人間なので、もちろんその当人のブログは綺麗で使いやすくなければいけないと数年間思っていました。

というわけで今回ははてなブログの見た目を綺麗にするにあたって苦労した点と楽勝だった点をお送りします。

綺麗にする前と後の比較

PC版はもともと綺麗な気がしたので、主にレスポンシブデザインによるスマートフォン表示の改善を頑張りました。

レスポンシブデザイン

f:id:gutcho:20170929101708p:plainf:id:gutcho:20170929101657p:plain
はてブロのモバイル版は何もしないとめちゃくちゃ殺風景なのでまずはレスポンシブモードをオンにしてユーザー定義のCSSを当てられるようにします。

開閉式のサイドバー

f:id:gutcho:20171027131235g:plainf:id:gutcho:20170929101702p:plain
通常のモバイル版だとブログ下端に現れるサイドバーですがボタンによる開閉式としました。もちろんno jQueryでstatelessなJavaScriptです。

はてブコメント欄

f:id:gutcho:20170929101724p:plainf:id:gutcho:20170929101719p:plain
はてなブックマークを垣間見れるブコメ欄、これ綺麗にするのにめちゃくちゃ苦労しました。

メニューボタンで開閉するメニューは15分で仕上がってしまい拍子抜けでしたが、ブコメ欄の改修はiframe罰非同期罰を食らったため2時間分の歯ごたえがありました。

カスタマイズの手順

f:id:gutcho:20171027200555p:plainf:id:gutcho:20171027200605p:plainf:id:gutcho:20171027200612p:plain
赤丸で囲われた部分を左から順にクリックしていきブログのヘッダおよびフッタのカスタマイズ画面に進みます。この2つにはHTMLを注入できるのでつまりCSSもJavaScriptも記述可能なのです。

なお、これからソースコードを交えた解説を行いますがあくまで僕の環境においての検証しか行なっておりませんのでもし皆様のブログに適用する際にはこの記事の内容をよしなに読み替えてください。

ソースコードと解説

開閉式のサイドバー

まずはソースをまるごと。これをヘッダのカスタム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);

以下、手順をおさらいです。

  1. id指定でスタイルタグ(<style id="js-bukome-style">)への参照を取得。
  2. ページ内に複数あるブコメ欄のiframeへの参照をclass名指定で全て取得。
  3. iframeへの参照を格納したオブジェクトをreduceメソッドの使用可能なArrayに変換。
  4. スタイル注入失敗時にtrueを返すreduceメソッドの初期値として第二引数にfalseを指定。
  5. 上書きに失敗するとtrueを返すoverwrite関数を配列の各要素に適用。
  6. reduceメソッドがtrueを返した場合、指定した間隔を置いてloop関数を再帰呼び出し。
  7. 全ての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()メソッドが返す値とは別にfoostring型と見做されます。

また、型アサーションは式であるため、括弧によって評価順を変更することも可能です。 例では、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の型が定まるため、実際に使用する際には以下のようなコードになります。

参考文献


以上、2項目でした。

項目は思いつきor要望があり次第、随時追加予定です。


  1. C#の主要開発者であるMicrosoft社はTypeScriptの生みの親

新しいブログを作りました。

ここの紹介です。

このブログにはプログラミングにまつわる内容の記事を投稿していきます。

今回は何故か放置せず更新頻度を保てそうな予感がします。

1発目に挨拶もなく普通の記事を投稿してしまい開設の挨拶は2番目となりましたが、どうぞよろしくお付き合いください。

「プログラムから使わないけど人間には役立つ資料群」の最適な保存先

結論

プログラム本体を保管するディレクトリ内に
関連ファイルはすべて保存しよう!

説明

手順

  1. 保存先ディレクトリ名を決定 (今回は仮に_MyDocsとする)
  2. ~/.gitignore_global_MyDocs/の指定を追加
  3. _MyDocsをプロジェクトルートに作成
  4. プロジェクト本体から独立したGitリポジトリ_MyDocs以下を管理
  5. 少しでも関連しそうなファイルは全てここにも保存

やったー!これで二度と資料を失くさずにすみますね!

~/.gitignore_globalのおかげでプロジェクト本体リポジトリへの間違いコミットも起きません。

余談

オススメ運用方針

  • 自分専用の領域として絶対に誰にも公開せず誰とも共有しない
  • 好き勝手にしていい適当で気軽な領域として扱う

上記2点を意識するだけで「資料置き場」としての価値が更に高まることと思われます。


以上、知ってる人には今更な、ドキュメント管理で迷わないため僕が実践してるアイディアについてでした。