ons-toolbarに配置したons-iconの位置ずれ

ons-toolbarにons-iconを配置すると、アイコンがツールバーの上下中央から少しずれてしまうことがあります。

HTML

<ons-page id="first-page">
  <ons-toolbar>
    <div class="center">Page 1</div>
    <div class="right">
      <ons-toolbar-button onclick="">
        <ons-icon icon="fa-cog" size="lg"></ons-icon>
      </ons-toolbar-button>
    </div>
  </ons-toolbar>
</ons-page>

この位置ずれについて調べました。

Note

Onsen UIFont Awesomeのお話になります。

CSSスタイルの優先順位

突然ですが、まず最初にCSSスタイルの優先順位について確認しておきます。
スタイルの優先順位 — HTMLクイックリファレンスを参考にざっくりまとめてみました。

  • よりタグに近い、より後から読み込まれたスタイルが優先される
  • セレクタの種類による優先順位
    • idセレクタ>classセレクタ>タイプセレクタ>全称セレクタ
    • 要素を特定したidセレクタ・classセレクタは、要素を特定しないidセレクタ・classセレクタより優先される
    • 優先順位が同じになった場合は最後に指定したスタイルが適用される
  • !important を付けると最優先になる

こうゆうのは文章を追うよりも実際にコードを見たり動かしたりしたほうが覚えますよね。そこで確認の意味も含め、私が考えたCSSクイズを出題したいと思います。

div要素は何色になるでしょう?

全部で2問。答えはこのページに直書きしているので、なるべくスクロールせず答えを隠して考えてもらえたらと思います。

第1問

style.css

.hoge {
  background-color: blue;
}

HTML

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>CSS TEST</title>
  <style>
  .hoge {
    background-color: yellow;
  }
  </style>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="hoge" style="width:300px; height:300px;"></div>
</body>
</html>
第2問

external.css

.piyo {
  background-color: red;
}

style.css

.hoge {
  background-color: blue;
}

HTML

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>CSS TEST</title>
  <link rel="stylesheet" href="external.css">
  <link rel="stylesheet" href="style.css">
  <style>
  div {
    background-color: yellow;
  }
  </style>
</head>
<body>
  <div class="hoge piyo" style="width:300px; height:300px;"></div>
</body>
</html>

答えを隠すためにスペースを入れますね。

もういいかな …

さて、正解は …

どちらもです。当たりました?

第1問は優先順位を「インライン>外部スタイルシート」と覚えていると間違えてしまいます。

第2問はclass属性内のクラス名の記述順序は優先順位に無関係ということです。意外と説明されていないようですので間違えやすいのではと思いました(私は間違えてました)。

位置ずれの原因と対策

本題に入ります。
ons-iconコンポーネントのsize属性はアイコンの大きさを設定します。値はlg, 2x, 3x, 4x, 5x, およびピクセルで、それぞれFont Awesomeのクラス名に対応しています。

font-awesome.css

.fa-lg {
  font-size: 1.33333333em;
  line-height: 0.75em;
  vertical-align: -15%;
}
.fa-2x {
  font-size: 2em;
}
.fa-3x {
  font-size: 3em;
}
.fa-4x {
  font-size: 4em;
}
.fa-5x {
  font-size: 5em;
}

fa-lgクラスだけ特殊のようです。font-sizeを親のフォントサイズの33倍にし、line-heightをその0.75倍しています。line-height = fontsize(親) × 1.33 × 0.75 = fontsize × (4/3) × (3/4) = fontsize、ということでline-heightを親のフォントサイズに合わせているんですかね?で、それを親のbaselineの位置から親のline-heightの15%分を下げると。うんうん、なるほど、さっぱりわかりません。実際やってみるとうまくいきますし、今年は夏でもほんとオーサムということでFont Awesomeを信じることにします。

ここで、sizeを lg, 2x, 24px にして表示を比べてみます。

HTML

<ons-page id="first-page">
  <ons-toolbar>
    <div class="center">Page 1</div>
    <div class="right">
      <ons-toolbar-button onclick="">
        <ons-icon icon="fa-cog" size="lg"></ons-icon>
      </ons-toolbar-button>
    </div>
  </ons-toolbar>
</ons-page>

size=”lg”

size=”2x”

size=”24px”

sizeに lg を指定したときだけずれます!

Debug ToolでDOM構造を確認すると、ons-iconコンポーネントは展開されてons-iconクラス、fa-cogクラス、faクラス、fa-lgクラスが付与されています。

index.html

<link rel="stylesheet" href="lib/onsenui/css/onsenui.css">

<ons-icon icon="fa-cog" size="lg" modifier="material" 
class="ons-icon fa-cog fa fa-lg"></ons-icon>

次に、上記index.htmlでリンクされているonsenui.cssを見てみます。ここでは、最初のほうでfont-awesome.min.cssのインポートを行っています。

onsenui.css(最初のほう)

@import url("font_awesome/css/font-awesome.min.css");

そして、その後に各コンポーネントのクラス定義などが記述されています。

つまり、Font Awesomeのスタイルを読み込んでからOnsen UIのスタイルを読み込むことになります。

ここで、ons-iconコンポーネントに付与されているons-iconクラスとfa-lgクラスを比べてみます。

font-awesome.css(最初に読み込む)

.fa-lg {
  font-size: 1.33333333em;
  line-height: 0.75em;
  vertical-align: -15%;
}

onsenui.css(後に読み込む)

.ons-icon {
  display: inline-block;
  line-height: inherit;
  font-style: normal;
  font-weight: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

Font Awesomeのfa-lgクラスとOnsenUIのons-iconクラスを一緒に指定すると、fa-lgクラスのline-heightの値がons-iconクラスのline-heightの値で上書きされます。

これが位置ずれの原因でした。

対策として自作のstyle.cssでline-heightを再度定義したところ、無事、上下中央に配置されました。

style.css

.fa-lg {
  line-height: 0.75em;
}

前述の第2問と同じで「class=”ons-icon fa-cog fa fa-lg”」と記述されていても fa-lg クラスではなく ons-icon クラスが有効になります。私はそこのところを勘違いしていて、なぜだろうと悩みながらline-heightやmargin、vertical-alignなどをこねくり回していました。

複数のライブラリを併用しているときに不思議な現象に出会ったら、ファイルの読み込む順番やスタイルの優先順位を確認するとデバッグの効率がよくなるかもしれませんね。

Monaca メモ

Monacaで久しぶりにプロジェクトを作成し Monaca デバッガを立ち上げたところ、以下のエラーが発生しました。

ググッてもヒットしなかったので私の使い方や環境が悪いだけかもしれませんが、プロジェクトの作成から解決するまでをメモっておきます。

開発環境

  • localkit v2.3.0 / Monaca IDE
  • Monaca Debugger v6.1.1
  • PC: Windows10

プロジェクト作成

以下の設定でプロジェクトを作成しました。

カテゴリ Onsen UI
テンプレート Onsen UI V2 JS Minimum

Monaca デバッガ立ち上げ

Monaca デバッガを立ち上げると、以下の2つのエラーがログに出力されました。

Uncaught ReferenceError: Set is not defined
www/lib/onsenui/js/onsenui.min.js: 3
Uncaught ReferenceError: ons is not defined
www/index.html: 16

なにが悪いのか想像すらつきません。
ちなみに localkit/IDEの Preview ではエラーはでていません。

ファイル構成とバージョン確認

index.html のソースをみてみます。

<script src="components/loader.js"></script>
<script src="lib/onsenui/js/onsenui.min.js"></script>

<link rel="stylesheet" href="components/loader.css">
<link rel="stylesheet" href="lib/onsenui/css/onsenui.css">
<link rel="stylesheet" href="lib/onsenui/css/onsen-css-components.css">

lib フォルダ内のファイルを開いてバージョンを確認すると、

onsenui.min.js v2.2.4
onsenui.css v2.2.4
onsen-css-components.css ?不明

次に loader ファイルを開いて中身を確認すると、

components/loader.js monaca-cordova-loader のコード
monaca-core-utils のコード
components/loader.css ヘッダのみ

Onsen UI のリリース情報を調べてみると最新版は v2.3.1。内容をざっと見ても本件と関連するような箇所はわからなかったのですが、v2.2.4 からけっこう更新されてました。

最新版 Onsen UI インストール

ということで、v2.2.4 以降のどこかで修正されていることを祈りつつ Onsen UI の最新版をインストールしてみることにしました。JS/CSSコンポーネントの追加画面で Onsen UI (Monaca Version)を選択し、選択可能なバージョンのなかで最新となる v2.3.0 をインストール。そして、Monaca デバッガで再実行。

エラーが消えました!!!

… が、よくみると 謎メッセージが2個?

ググったところ Shadow DOM を使っていなければ気にしなくていい的なことが書いているように見えた(英語わかりません)ので無視することに。

でも、なんで2個?

index.html 見直し

index.html を見てみると loader ファイルを読み込んでから lib フォルダ内のファイルを読み込んでいます。

そこで、もう一度 loader ファイルを確認してみます。

components/loader.js monaca-cordova-loader のコード
monaca-core-utils のコード
monaca-onsenui のコード追加
components/loader.css onsenui.css と onsen-css-components.css のインポート文追加

loader ファイルに Onsen UI の記述が追加されています。バージョンは v2.3.0。

次に lib フォルダ内のファイルを開いてバージョンを確認すると、js ファイル/css ファイル ともに v2.2.4 のまま変わりありません。

ということは loader ファイルで v2.3.0 を読み込んだ後に lib フォルダ内の v2.2.4 を読み込んでる!
だから同じ内容のメッセージが2個表示されたんですね。

index.html 修正

ということで index.html の lib フォルダ内のファイル読み込みをコメントアウトしました。

  <script src="components/loader.js"></script>
<!--
  <script src="lib/onsenui/js/onsenui.min.js"></script>
-->
  <link rel="stylesheet" href="components/loader.css">
<!--
  <link rel="stylesheet" href="lib/onsenui/css/onsenui.css">
  <link rel="stylesheet" href="lib/onsenui/css/onsen-css-components.css">
-->

実行した結果は以下のようになりました。

まとめ

JS/CSSコンポーネントの追加画面から追加したファイルは loader ファイルに書き込まれます。Onsen UI を追加(更新)した場合は、index.html に前バージョンのファイルの読み込みを行う script 文が残っているので、これを手動で削除する必要があります。

以前は Onsen UI は最初から loader ファイルに記述されていて、 index.html には Onsen UI を読み込む script 文はなかったと思うんです。今後また仕様が変わるかもしれませんが現状報告ということで。

ons-input について

私のいる地域(岩手沿岸)では台風10号の影響で健康診断が一か月延期になりました。そして今日、待ちに待ってない健康診断に行ってきました。外で順番待ちの間とても寒かったのと、血を抜かれたのとでだるだるです。ということでリハビリがてらブログ更新です。今週のアプリのアップデート作業はほとんど進まず。おもしろいようにハマるので、もう無理せず行くことにしました。転んでただで起きるのは損なので、この一週間でハマったことをネタにします。

HTML/CSS 編

その1

display: black;

正解は「block」。どうやら blackを指定してもディスプレイは黒くならないようです。勉強になりました。

その2

<div style="margin:10px;width:100px;height:100px;" class="..." style="padding:10px">

デバッガで直接要素のスタイルを書き換えてレイアウトやデザインを調整しているとき、style属性に値を設定しても反映されず悩みました。よく見ると前のほうにすでにstyle属性が。これはきっと属性を複数使っちゃダメということですね。上記の例では2つ目の「style=”padding:10px”」が無効になりました。

ons-input type=”radio” 編

その1

これまでラジオボタンは input要素で作成していましたが、今回 Onsen UI v2を使用するにあたり、はじめて ons-input 要素を使ってみました。そこで謎の現象が。マテリアルデザインのラジオボタンをタップすると、checkedが付加されることでCSSのアニメーション処理が発生し、緑の丸がscale(0)からscale(1)に拡大します。心地よいアニメーションです。ところが、IDEのプレビュー動作はスムーズなのですが、Monacaデバッガでは緑の丸が表示される前に四角形が一瞬表示され、ちらついて見えます。私だけ?
ソースを出すほどではありませんが、とりあえず載せてみます。

... 略 ...
  <script src="lib/angular/angular.min.js"></script>
  <script src="lib/onsenui/js/onsenui.min.js"></script>
  <script src="lib/onsenui/js/angular-onsenui.min.js"></script>
... 略 ...
<body>
  <ons-page>
    <ons-toolbar>
      <div class="center">radio button - test</div>
    </ons-toolbar>

    <div style="margin: 10px;">
        <ons-list>
            <ons-list-item tappable>
                <label class="left"><ons-input name="g1" type="radio" input-id="r1" checked></ons-input></label>
                <label for="r1" class="center">radio 1</label>
            </ons-list-item>
            <ons-list-item tappable>
                <label class="left"><ons-input name="g1" type="radio" input-id="r2"></ons-input></label>
                <label for="r2" class="center">radio 2</label>
            </ons-list-item>
            <ons-list-item tappable>
                <label class="left"><ons-input name="g1" type="radio" input-id="r3"></ons-input></label>
                <label for="r3" class="center">radio 3</label>
            </ons-list-item>
        </ons-list>
    </div>
  </ons-page>
</body>
</html>

いろいろ調べたり試しました。ネットで検索していると「:checked擬似クラスの隣接セレクターのWebkit系での挙動とか」や、そこで紹介されている「Chrome/webkit not rendering css display change on input:checked + element + element」という記事を見つけ、これで解決と喜び勇んで隣接セレクタを間接セレクタに変更してみたのですが効果なしでした。
結局、よく分からないながらも以下のようにチェック前の内側(:after)のopacityを1から0に変更することで四角形の表示を抑止できました。

.radio-button--material__input + .radio-button__checkmark:after {
    opacity: 0;
}

これでうまくいってそうに見えますが、ダイアログ上のラジオボタンで試すとアニメーションが発生しないようです。別問題かもしれませんけど。とりあえず、私としては四角形はこれで防いで、アニメーションは最悪なくてもいいかなと思ってます。ほんとは原因・対策がはっきりするのが一番いいのですが、留意事項ということで。

その2

input 要素でラジオボタンを作成していた時は、以下のように document.getElementsByName() で input要素にアクセスしていました。

// チェックしている項目を取得
var radioList = document.getElementsByName("background");
for(var i=0; i<radioList.length; i++){
  if (radioList[i].checked) {
    ...
  }
}
// チェックする
var radioList = document.getElementsByName("background");
radioList[mdt.setting.backgroundType].checked = true;

ここで、ons-inputにname属性を設定し上記と同様にアクセスすると、うまくいきませんでした。
原因は、ons-input に設定した name属性が、ons-input の子要素として生成される input要素にも設定されるため、document.getElementsByName()で取得した配列には input要素と ons-inputコンポーネント両方が格納されるからです。マニュアルの属性欄にはnameの記載がありませんが実例の5ページ目でname属性を使用していますし、name属性はあったほうが自然だと思います。ons-inputの input-id属性と同じように input-name属性を用意してinput要素にだけ name属性を付けるようにするといいような気がします、素人考えですが。
現状でラジオボタンへアクセスするために、ラジオボタンの親要素にidを追加し、document.getElementsByName() を document.querySelectorAll() に変更してアクセスするようにしました。

var radioList = document.querySelectorAll("#background-list input");

なんかほかにもあったような気がしますが今日はここまで。