20200320のCSSに関する記事は13件です。

Electronでカレンダーを作る③

前回からの続き

前回はカレンダーの中身が動的に表示されるようにした。

表示する月を変えられるようにする

先月、来月に切り替えられるようにしたい。

先月と来月切り替えボタンを配置する。

index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>ElectronCalendar</title>
  <script text="text/javascript" src="./js/index.js"></script>
  <link rel="stylesheet" href="./css/index.css">
</head>
<body>
  <div class="calendar-wrapper">
    <div class="preMonthButton">
      <button type="button" id="preMonth"><先月</button>
    </div>
    <div class="nextMonthButton">
      <button type="button" id="nextMonth">来月></button>
    </div>
    <table id="table" class="calendar">
      <caption id="caption"></caption>
      <thead>
        <tr>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
        </tr>
      </thead>
      <tbody>
        <tr id="row1">
          <td id="cell1"></td>
          <td id="cell2"></td>
          <td id="cell3"></td>
          <td id="cell4"></td>
          <td id="cell5"></td>
          <td id="cell6"></td>
          <td id="cell7"></td>
        </tr>
        <tr id="row2">
          <td id="cell8"></td>
          <td id="cell9"></td>
          ・・・略
          <td id="cell33"></td>
          <td id="cell34"></td>
          <td id="cell35"></td>
        </tr>
        <tr id="row6"> ※
          <td id="cell36"></td>
          <td id="cell37"></td>
          <td id="cell38"></td>
          <td id="cell39"></td>
          <td id="cell40"></td>
          <td id="cell41"></td>
          <td id="cell42"></td>
        </tr>
      </tbody>
    </table>
  </div>
</body>
</html>

※カレンダーに6行目を追加している。

index.js
'use strict';

const moment = require("moment");

window.onload = function() {
    createCallendar(moment());

    //先月ボタン押下時
    document.getElementById('preMonth').onclick = function() {
        createCallendar(moment(this.value));
    }

    //来月ボタン押下時
    document.getElementById('nextMonth').onclick = function() {
        createCallendar(moment(this.value));
    }
}

/**
 * カレンダーを表示する。
 * @param momentオブジェクト 
 */
const createCallendar = function(localMoment) {
    //captionを表示
    document.getElementById('caption').innerText = localMoment.format("YYYY年MM月");

    //カレンダー初期化
    clearCallendar();

    //当月の日数を取得
    const daysOfMonth = localMoment.daysInMonth();

    //月初の曜日を取得(index.htmlと合わせるために+1する)
    const firstDayOfManth = localMoment.startOf('month').day() + 1;

    //カレンダーの各セルに日付を表示させる
    let cellIndex = 0;
    for(let i = 1; i < daysOfMonth + 1; i++) {
        if(i === 1) {
            cellIndex += firstDayOfManth;
        } else {
            cellIndex++;
        }
        document.getElementById("cell" + cellIndex).innerText = i;
    }

    //6行目の第1セルが空白なら6行目自体を非表示にする。
    if(document.getElementById("cell36").innerText === "") {
        document.getElementById('row6').style.visibility = "hidden";
    }

    //先月
    document.getElementById('preMonth').value = localMoment.add(-1,'month').format("YYYY-MM");
    //来月(先月のmomentオブジェクトとなっているので+2ヶ月)
    document.getElementById('nextMonth').value = localMoment.add(2,'month').format("YYYY-MM");
}

/**
 * カレンダーを初期化する
 */
const clearCallendar = function() {
    //6行目を表示させておく
    if(document.getElementById('row6').style.visibility === "hidden") {
        document.getElementById('row6').style.visibility = "visible";
    }
    for(let i = 1; i < 43; i++) {
        document.getElementById("cell" + i).innerText = "";
    }
}

6行目は表示しない場合がある(表示しない場合の方が多い)ので、表示しない場合は隠す。

$ electron .

初期表示
image.png

6行目がある場合
image.png

とりあえず出来たけどボタンがダサい。

TODO

・月を切り替えた場合、同一html内の表示内容の切り替えとなっているため、いちいち初期化が必要で微妙。新しく画面を読み込むようにしたい。
・ボタンの見た目をナウい感じにしたい。

あとがき

Electronの記事じゃ無く、HTMLとJSの記事みたいになってる・・・。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

レスポンシブにしたのに、なぜかなっていない。そんな時の確認。

せっかくレスポンシブに対応しているようにコードを書いたのに、サーバーにあげるとレスポンシブになっていない。

結論 head要素を見ましょう

ヘッド要素の中(railsならapplication.html)の中身に

<meta name="viewport" content="width=device-width, initial-scale=1.0">

hamlなら

%meta{:content => "width=device-width, initial-scale=1.0", :name => "viewport"}/

こちらを記入しましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Electronでカレンダーを作る②

前回からの続き

前回は画面まで作成した。
カレンダーの内容を動的に表示する処理を作成していく。

カレンダーの中身を作っていく

日付操作を楽にするためmomentモジュールを使う。

$ npm install -D moment

index.htmlを修正していく。

index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>ElectronCalendar</title>
  <script text="text/javascript" src="index.js"></script>
  <link rel="stylesheet" href="index.css">
</head>
<body>
  <div class="calendar-wrapper">
    <table class="calendar">
      <caption id="caption"></caption>
      <thead>
        <tr>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td id="cell1"></td>
          <td id="cell2"></td>
          <td id="cell3"></td>
          <td id="cell4"></td>
          <td id="cell5"></td>
          <td id="cell6"></td>
          <td id="cell7"></td>
        </tr>
        ・・・略
          <td id="cell27"></td>
          <td id="cell28"></td>
        </tr>
        <tr>
          <td id="cell29"></td>
          <td id="cell30"></td>
          <td id="cell31"></td>
          <td id="cell32"></td>
          <td id="cell33"></td>
          <td id="cell34"></td>
          <td id="cell35"></td>
        </tr>
      </tbody>
    </table>
  </div>
</body>
</html>

7(曜日) × 5(週)の箱を作って各セルにIDを振っておく。

index.js
'use strict';

const moment = require("moment");

window.onload = function() {
    //captionを表示
    document.getElementById('caption').innerText = moment().format("YYYY年MM月");

    //当月の日数を取得
    const daysOfMonth = moment().daysInMonth();

    //月初の曜日を取得(index.htmlと合わせるために+1する)
    const firstDayOfManth = moment().startOf('month').day() + 1;

    //カレンダーの各セルに日付を表示させる
    let cellIndex = 0;
    for(let i = 1; i < daysOfMonth + 1; i++) {
        if(i === 1) {
            cellIndex += firstDayOfManth;
        } else {
            cellIndex++;
        }
        document.getElementById("cell" + cellIndex).innerText = i;
    }
}

月初の曜日が分かればそれ以降の日付の曜日も一意に決まる。
カレンダーの特性上、1番上の行のセルには値が必ず一個は入るので、cellIndex += firstDayOfManth; で初日のセルの位置を設定して、後は日数分インクリメントしていく。

月初はmomentモジュールのmoment().startOf('month')で楽に取得できる。

$ electron .

image.png

祝日を赤字にするにはどっかでデータを持っておかないと無理そう。

TODO

年月を変更できるようにする。

あとがき

momentモジュールが便利。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsのtext_fieldにCSSをあてる方法

text_fieldにCSSってどうやってあてるんだっけ?

Railsアプリケーション作成時にフロント部分を作成していた時に

example.haml.html
.item__name
  商品名
  .name--input
    = f.text_field :name, placeholder: "40文字まで"

のようなhamlを作成していざscssを記述しようとしたときに
このtext_filedにどうやってCSSをあてるのかが
ふと考えると分からなかったんです。

同じようなnumber_fieldsubmitなどにも使えるやり方なので
是非覚えておいたほうがよいです。

そもそもtext_fieldで作られるHTMLは何なのか?

上の= f.text_field :name, placeholder: "40文字まで"で作られるHTMLを
chromeの検証で確認してみると
<input placeholder="40文字まで" type="text" name="item[name]" id=item_name>
というものが作成されているのがわかるかと思います。

text_fieldとかはRailsがviewを簡潔に記述するために用意してくれているヘルパーメソッドのため
簡単な記述で実際はこういうHTML文の作成もしてくれています。
この作成されたHTMLにあてるようにCSSを記述すればOKです。

今回はSCSSを使い記述しました。

CSSをあててみよう

example.scss
.item__name{
  input[type="text"]{
    width: 100%;
    height: 20px;
    font-size: 14px;
  }
  ::placeholder{
    padding: 5px 5px;
  }
}

と指定して記述すると、text_fieldにCSSをあてることができます。
placholderはCSSの擬似要素のため上記のような記述をする必要があります。

ヘルバーメソッドを使って記述した場合にどのようなHTML文が作成されているのかを確認すると解決できる部分でしたね。

参考先

rails フォームの大きさをcssで変更する

CSS: カスケーディングスタイルシート::placeholder

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Electronでカレンダーを作る①

はじめに

皆さん、Electron触っていますでしょうか?
私は最近Electronの存在を知ったので、JavaScriptの勉強も兼ねてカレンダーアプリを作成してみようと思った次第です。

とりあえずElectronプロジェクトを作る。

$ mkdir ElectronCalender
$ cd ElectronCalender/
$ npm init -y
$ npm install -D electron ※

これでディレクトリ内にElectronを使う準備は整った。
※npm install -D ~ はpackage.jsonに書き込みらしい。

画面を作ってアプリを立ち上げる。

package.jsonにエントリーポイント(main.js)を指定しとく。

package.json
{
  "name": "ElectronCalender",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "electron": "^8.1.1"
  }
}

main.jsの中身

main.js
'use strict';

// Electronのモジュール
const electron = require("electron");

// アプリケーションをコントロールするモジュール
const app = electron.app;

// ウィンドウを作成するモジュール
const BrowserWindow = electron.BrowserWindow;

// メインウィンドウはGCされないようにグローバル宣言
let mainWindow;

// 全てのウィンドウが閉じたら終了
app.on('window-all-closed', function() {
  if (process.platform != 'darwin') {
    app.quit();
  }
});

// Electronの初期化完了後に実行
app.on('ready', function() {
  // メイン画面の表示。ウィンドウの幅、高さを指定できる
  mainWindow = new BrowserWindow({width: 800, height: 600});
  mainWindow.loadURL('file://' + __dirname + '/index.html');

  // ウィンドウが閉じられたらアプリも終了
  mainWindow.on('closed', function() {
    mainWindow = null;
  });
});

mainWindow.loadURL('file://' + __dirname + '/index.html'); で表示する画面(htmlファイル)を読み込む。

html,cssはこんな感じ(ほぼ参考書の内容を丸写しした)

index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>ElectronCalender</title>
  <link rel="stylesheet" href="index.css">
</head>
<body>
  <div class="calendar-wrapper">
    <table class="calendar">
      <caption>2020年3月</caption>
      <thead>
        <tr>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>1</td>
          <td>2</td>
          <td>3</td>
          <td>4</td>
          <td>5</td>
          <td>6</td>
          <td>7</td>
        </tr>
        <tr>
          <td>8</td>
          <td>9</td>
          <td>10</td>
          <td>11</td>
          ・・・略
        </tr>
      </tbody>
    </table>
  </div>
</body>
</html>
index.css
.calendar-wrapper {
    width: 750px;
}

.calendar {
    table-layout: fixed;
    width: 100%;
    border-collapse: collapse;
    border: 1px solid #ccddcc;
    vertical-align: middle;
}

.calendar th,
.calendar td {
    padding: 10px 0;
    border: 1px solid #cdcdcd;
    text-align: center;
    vertical-align: top;
    font-size: 35px;
}

.calendar th {
    background-color: #dedede;
}

/* 日曜日1行目(見出し) */
.calendar th:first-child {
    background-color: #e05557;
    color: #ffffff;
}

/* 日曜日 */
.calendar td:first-child {
    color: #e05557;
}

/* 土曜日1行目(見出し) */
.calendar th:last-child {
    background-color: #207bcf;
    color: #ffffff;
}

/* 土曜日 */
.calendar td:last-child {
    color: #207bcf;
}

/* 祝日 */
.holiday {
    color: #e05557;
}

.calendar caption {
    margin: 0 0 10px 0;
    padding: 10px;
    font-size: 23px;
    border: 5px solid #dedede;
    border-radius: 30px;
    font-weight: bold;
}

アプリを起動する。

ディレクトリ直下で
$ electron .

image.png

良い感じな気がする。

TODO

日付、祝日をhtml内にベタ書きしているのでjsを使って動的にカレンダーの内容が作成されるようにしたい。

あとがき

作っていて気がついたけどカレンダーのスペルは「calender」じゃなくて「calendar」だった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでページ毎にsubmitの表記を変えたい

submitボタンの表記を変えたい

submitボタンを実装した場合、デフォルトの日本語表記は下記のような形で表示されます
(色付けなどはCSSで加工済み)

スクリーンショット 2020-03-20 14.09.19.png

'登録する' というのがデフォルトのsubmitボタンとなります。

例えば、作成しているアプリで商品出品するページでは当然、'出品する'という表記にしたいですよね。
しかし、商品を編集して更新する場合は、'出品する'だと違和感があるので
'更新する'と表記をしたいところです。

しかし、出品も更新も同じフォームを流用しているので
分岐分けで記入する必要があるところになります。

単純にsunmitボタンのテキストを変えたい場合なら
<input type="submit" value="出品する">
を使えば表記は変わるわけなのですが、上記のように分岐わけさせたいときにどうするべきか。
出品と更新のページを別々に作って、各々でテキスト表記を変えるという手段もできますが
メンテナンス上、ほぼ同じ記述のコードのファイルが増えてしまうのは避けたいところです。

ja.ymlを利用して分岐させる

ja.ymlといえば、英語表記を日本語化させる指定を記述する場所なのですが
こちらでアクション毎にsubmitの表記を指定させることができます。

まずはi18nの導入が必要なので参考先にあるページを参照に導入を済ませておいてください
[初学者]Railsのi18nによる日本語化対応

config/locales/ja.ymlに記述する

ja.yml
ja:
  helpers:
    submit:
      create: "出品する"
      update: "更新する"

このように記述をするとcreate下のsubmitは出品すると表示され
update下のsubmitは更新すると表示されるようになります。

実際にcreate時はこのように変更がかかります。
スクリーンショット 2020-03-20 14.09.41.png
update画面も同様に記述した通りの変更がかかります。

これでhtmlをcreateとupdateで別々に表記を分ける必要がなくなるので
ファイルを増やすことなく分岐して表示させることができます。

参考先

[初学者]Railsのi18nによる日本語化対応

http://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-submit

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[scss]hamlへの反映方法は2種類あった 備忘録

今までscssのhamlへの反映方法といったら
application.scss@import記述のみと思っていましたが。

実はそんなことをしなくてもコマンド入力で作成したscssには
自動的に反映される記述が入っているんです。
知りませんでした。。。
コメントアウトされ得た以下の3行があるとOKらしい

scss
// Place all the styles related to the reset controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

ですので、scssの反映方法を以下2種類をまとめたいと思います

コマンドで作成する方法

importいらないコマンド存在したー!!!

ターミナル
$ rails g assets 作成したいファイル名

以上です。
あとはscssに記述したものは自動的にhamlに反映されます。

手動でファイルを作成する方法

右クリックでファイル作成をした場合scssにimport記述は自動生成されませんので
application.scssにimportしてhamlにscssが当たるようにしましょう。

やり方
・hamlに反映させたいscssのファイル名をアンダーバー「 _ 」始まりにします。
application.scssにimportします。
・終わり

記述参考は下記

application.scss
@import "home";
@import "いれたいscssファイル名(アンダーバーは入れない)"; ⬅︎終わりは必ずセミコロンを入れる

#assetsの中でもフォルダの異なるものはURLをちゃんと記載しないと反映されない
@import "config/reset";
@import "フォルダ名/ファイル名";

参照記事

Railsドキュメント

終わりに

自分のいた学校は極力コマンドを使用しない学校でしたが
コマンドはコマンドで別の仕様があることを今回知りました。
アウトプットもそうですがインプットも必要。
人との情報交換は本当に大事だなと改めて感じました。

初学者な為、記事に不備やアドバイ等ございましたらご連絡頂けますと幸いです。
最後まで読んできただきありがとうございます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

css と簡単なjqueryでメニューを開閉する

久々に時間が取れたので、頭の整理も兼ねて書いてみることにしました。
フロントをメインに仕事をしていますが、cssをうまく利用すると簡単な動きなら表現できることを知ったので、その一例を書いてみます。
今回はヘッダーとかによくあるメニューを作ってみましょう。

目的

ボタンとリストを作成し、ボタンをクリックするとリストが開閉するようにcssとjqueryで調整する。

html

htmlはボタンとリストだけのシンプルなもの、reset.cssの適用をお忘れなく。

index.html
<html lang="jp">
    <head>
        <meta charset="utf-8"/>
        <link href="css/reset.css" type="text/css" rel="stylesheet" />
        <link href="css/open1.css" type="text/css" rel="stylesheet" />
        <script src="js/jquery-3.2.1.min.js" type="text/javascript"></script>
        <script src="js/open1.js" type="text/javascript"></script>
    </head>
    <body>
        <div class="btn"></div>
        <ul>
            <li>リスト1</li>
            <li>リスト2</li>
            <li>リスト3</li>
        </ul>
    </body>
</html>

css(scss)

scssでかくとこんな感じです。ポイントは2つあります。
一つ目はulです。height:0;overflow:hidden;をつけることで中身のliが見えなくなります。また、transitionでheightが変更された時に時間をかけて変化してくれます。
もう一つのポイントは&.on(body.on)です。
ここにボタンを押した時の変化を記述します。

open1.scss
body{
    width:100%;
    height:100%;
    background-color: rgb(220,220,220);
    .btn{
        width:100px;
        height:100px;
        background-color: blue;
    }
    ul{
        width:200px;
        height:0;
        overflow: hidden;
        transition: height 1s;
        li{
            width:100%;
            height:60px;
            border: 1px solid #000;
        }
    }
    &.on{
        background-color: rgb(150,150,150);
        .btn{
            background-color: red;
        }
        ul{
            height:186px;
        }
    }
}

jquery

かなりシンプルに書いています。ボタンを押した時に、bodyタグにクラスを足すor消すだけ。

open1.scss
$(function(){
    $(document).on("click", ".btn", function(){
        if(!$('body').hasClass('on')){
            $('body').addClass('on');
        }else{
            $('body').removeClass('on');
        }
    });
});

動作説明

ボタンを押すとjqueryが発火し、bodyにonのクラスを足します。
すると、cssのbody.onが適用され、下記の変化が起こります。

1.bodyの背景色が変更される
2.ボタンの背景色が変更される
3.ulの高さが変わったため、一秒間のアニメーションをへて変化

もう一度ボタンを押すと元に戻ります。
ね、簡単でしょ?

このように、ボタンのオンオフくらいのアニメーションならcssをメインにするとコード量が減り、jqueryの負担も軽くなります。複数の言語を組み合わせると面白いことが結構あるので、色々試してみるといいでしょう。
次はphpとjqueryを組み合したものを紹介できたらなと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UIライブラリに学ぶz-indexの設計

背景

z-indexをどのように設定していますか?
はじめのうちは、一般コンテンツよりヘッダーが前で、オーバーレイはさらにその前で、モーダルはもっと前で。。。と、ざっくりと相対的に決めてしまえば問題なく実装できると思います。

しかし、アプリケーションが大きくなってきたり、新しい開発者がどんどん入ってきたりすると、オレオレだと辛くなってきます。
私も無根拠に z-index: 99999; などと書いてしまっていましたが、「何かルールを設けては」と指摘されてしまいました。

デファクトになっているようなガイドラインはないっぽいので、
有名UIライブラリやOSSはどう設計しているのか、参考にするために調べてみました。

CSSフレームワーク/UIライブラリ

Bootstrap

言わずとしれたBootstrapは次のように設計されています。

$zindex-dropdown:          1000 !default;
$zindex-sticky:            1020 !default;
$zindex-fixed:             1030 !default;
$zindex-modal-backdrop:    1040 !default;
$zindex-modal:             1050 !default;
$zindex-popover:           1060 !default;
$zindex-tooltip:           1070 !default;

上記にないコンテンツは0~999でということでしょう。
次いでドロップダウンメニュー、スティッキーヘッダーと続いています。

(ソース) https://getbootstrap.jp/docs/4.2/layout/overview/#z-index

Semantic UI

Semantic UIも人気のCSSフレームワークです。

Semantic UIはドキュメント等にz-indexの指標はまとまっていませんでしたが、
https://github.com/Semantic-Org/Semantic-UI/search?q=zIndex&unscoped_q=zIndex
を見る限り、ざっくり以下のような感じかと思います。

メインコンテンツ: ~200くらい
ヘッダー: 800
プログレスバー: 999
オーバーレイ: 1000
モーダル: 1001
ポップアップ: 1900

Bootstrapほどちゃんと決まってない感じもしますが、数字のオーダー的にはBootstrapと似たような感じです。

Material-UI

人気のReactベースなUIコンポーネントライブラリです。

mobile stepper: 1000
speed dial: 1050
app bar: 1100
drawer: 1200
modal: 1300
snackbar: 1400
tooltip: 1500

Bootstrapと似た設計です。
0~999は一般のコンテンツなのでしょう。
モーダルを表示したときのオーバーレイなどは、drawerとmodalの間の1201~1299のどこかであることが推測できます。

(ソース) https://material-ui.com/customization/z-index/

UIライブラリまとめ

なんとなくUIライブラリではどのようにz-indexが設計されているのかがわかりました。

1000未満程度はテキストフィールドやボタンなど、一般的なコンテンツが占めるようにしていて、
1000以上でフローティング要素などを、その役割順に設定していっています。
Semantic UIのように、1しかz-indexの値を足さない事もあれば、10~100程度足して余裕をもたせることもあるようです。

OSS(Web)

次に、有名なOSSではどんな感じでz-indexを設定しているのか見てみます。

GitLab

GitLabはOSSでコードを公開しています。
ざっくり以下のような感じです。

popover: 240
dropdown: 300
notification-message: 999
performance-bar: notification-message + 1
footer-message: 1000
header: 1000
overlay: 1031

popoverが結構低層で、dropdownメニューとかぶったとき下に行ってしまいますが、あまり起こり得ないので問題ないのでしょう。
performance-bar(進捗バー的なもの?)は、scssの定数で、notification-messageよりひとつ上に表示するようになっています。
このように、ある程度のルールだけ定数で持っておいて、細かい部分は+1するといった運用は実践的に思えます。

(ソース) https://github.com/gitlabhq/gitlabhq/search?q=z-index&unscoped_q=z-index

Mattermost(Web版)

MattermostはSlackライクなチャットツールです。
OSSなのでオンプレで運用できます。
Slackがセキュリティ上だめだから、Mattermostを自前のサーバに立てて使ったりしてる会社もあります。

dropdown: 100
popover: 999
overlay: 1000
modal: 1030
その他フローティング要素(emoji pickerなどいろいろ): 1050あたりでちょこちょこ
その他最上位要素(通知などいろいろ): 9999

だいたいこんな感じでした。
こちらはGitLabと比べて、あまりしっかりとルールがあるようには思えませんでした。
9999が最上位ということは決まってるっぽいですが、9999の要素がいくつもあります。

(ソース) https://github.com/mattermost/mattermost-webapp/search?q=z-index&unscoped_q=z-index

調査を踏まえて

今回調べたことを踏まえて、開発中のWebサービスのz-index設計は以下のようにしてみました。

~ 0             : 背景要素
~ 1000          : 主なコンテンツ
1001 ~ 1099     : フローティング要素
1100            : ヘッダー
1150            : Overlay(ドロワー背面)
1200            : ドロワー、サイドバー
1250            : Overlay(ドロワー前面)
1300            : モーダル
1400            : Snackbar(通知ポップアップ)
1500            : Tooltip(要素の説明ポップアップ)

上記すべて使うかはわかりませんが、プロジェクトが大きくなる前に指標を決めておくのは有効そうです。
実際の運用では、theme.tsなどと、テーマ用のファイルを作って、そこですべて定義してテーミングします。

これがだけが絶対的な値となり、これ以外にz-indexを設定する必要がある場合は、以下のように相対的に設定することで、cssのマジックナンバーをなくします。

z-index: ${theme.zIndex.overlay + 1};

何かUIライブラリを入れている場合は、そのライブラリのz-indexの設計を調べて、それを踏襲すると良いかと思います。
(自前ルールにライブラリの方を合わせるためにオーバーライドする手間がかかるため)

このように設計することで、統一感があり堅牢なcssになるかと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者によるプログラミング学習ログ 267日目

100日チャレンジの267日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。
100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
267日目は、

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

lighten(red, 50%) はSassでは真っ白、Stylusでは薄赤

概要

SassとStylus、両CSSプリプロセッサには lighten() という同名の関数がありながら、その処理は実は違う。
結果の違いや原理の違いを把握し、ついでなのでそれぞれに互換する独自関数を作ってみる。

関数紹介

lighten(color, amount)

  • color:明るくする前の色。red とか、#ff0000 とか。
  • amount:明るくする量。本記事では簡単のために、単位を 10% とかのパーセントのみ、範囲を0~100のみとする。

Sassの挙動

lighten.sass
*
  color: lighten(red, 50%)
  // => color: white;

Stylusの挙動

lighten.styl
*
  color: lighten(red, 50%)
  // => color: #ff8080;

※ 執筆時点で、QiitaのシンタックスハイライトはStylusに未対応

違うね…

  // => color: white; // Sass
  // => color: #ff8080; // Stylus

違う。

違いに直面した当初は、自分が何をミスってるのかと焦った。
色空間の違いか? と思ったりもしたけど、調べたところどちらもHSL色空間の輝度(Lightness)を増やす関数なので、値の扱いは同じはず1
結果的には、どのように明るくするかという仕様そのものの違いだった。

なお、併せて述べていくが、暗くする darken() や、彩度を変える saturate()desaturate() も、やはり両言語間で仕様が違う。

何が違うか

以下、公式情報を概訳しながら拾い上げていく。

Sassの lighten()

公式ドキュメント:
https://sass-lang.com/documentation/modules/color#lighten

ご丁寧に目立つ注意書きがある。

⚠️ ちょっと待った!
lighten() は、輝度の値を固定的に増加させる関数です。お望みの効果ではない場合があります。

// #e1d7d2の輝度は85%なので、lighten()関数で30%の輝度を足すとただの白になる
@debug lighten(#e1d7d2, 30%)  // white
// 輝度 46% → 66%
@debug lighten(#6b717f, 20%)  // #a1a5af

// 輝度 20% → 80%
@debug lighten(#036, 60%)  // #99ccff

// 輝度 85% → 100%
@debug lighten(#e1d7d2, 30%)  // white

つまり、引数がパーセント値でありながらその実態は「旧パーセント値そのものを amount だけ底上げする」であり、「旧パーセント値を amount の割合だけ増加させる」ではない(伝わるかな…)。
お望みではないかも、というのはこういうことだろう。

なお一般的に、パーセント値そのものが差分的に変動する場合、その差分の単位の名称はパーセントではなくポイント(パーセントポイント、percentage point)となる。
例えば20%が25%になったとしたら、それは「5%増えた」ではなく「5ポイント増えた」という。
何せ、値としての20が5%増えたら $20 \times 1.05 = 21$ になるので、その紛らわしさを回避するためにはそりゃ必要な呼び分け方だろうなという感じがする。
…いやしかし、CSS的には大きさの単位に pt(ポイント)があるからやっぱ紛らわしいな……。

ということで、Sassの lighten() は元の色の輝度に対してポイント的処理をしている、といえる。

タイトルの例について改めて触れると、

lighten.sass(再掲)
*
  color: lighten(red, 50%)
  // => color: white;

HSL色空間では red 等の原色はその時点で既に50%の輝度を持つため、Sassの lighten(red, 50%) で輝度が更に50ポイント足された結果、輝度100%の white が得られる。

お仲間の関数について

暗くする darken() や、彩度を変える saturate()desaturate() についても同じであり、ドキュメントにも全く同様の注意書きがある。

Stylusの lighten()

公式ドキュメント:
https://stylus-lang.com/docs/bifs.html#lightencolor-amount
特に注意書きは無い。

オープンソースなので、ソースを覗いてみる。
https://github.com/stylus/stylus/blob/master/lib/functions/index.styl#L125

index.styl
// 与えられた amount で暗くする

darken(color, amount)
  adjust(color, 'lightness', - amount)

// 与えられた amount で明るくする

lighten(color, amount)
  adjust(color, 'lightness', amount)

暗くする方と共に adjust() という関数で定義されており、その差は amount の正負にある模様。

更に adjust() のソースを覗いてみる。
https://github.com/stylus/stylus/blob/master/lib/functions/adjust.js#L13

adjust.js(抜粋)
function adjust(color, prop, amount){
  utils.assertColor(color, 'color');
  utils.assertString(prop, 'prop');
  utils.assertType(amount, 'unit', 'amount');
  var hsl = color.hsla.clone();
  prop = { hue: 'h', saturation: 's', lightness: 'l' }[prop.string];
  if (!prop) throw new Error('invalid adjustment property');
  var val = amount.val;
  if ('%' == amount.type){
    val = 'l' == prop && val > 0
      ? (100 - hsl[prop]) * val / 100
      : hsl[prop] * (val / 100);
  }
  hsl[prop] += val;
  return hsl.rgba;
};

輝度操作のコア部分の処理をグラフで表すとこんな感じ。
https://www.desmos.com/calculator/m5j1th0unt
stylus-lighten.png
緑のラインは元の色の輝度。
入力値が正の領域は lighten() に、負の領域は darken() に用いられている。

見ての通り、これはSassとは違って「旧パーセント値を amount の割合だけ増加させる」ような計算になっている。
この計算なら、元の色によらず常に真っ白や真っ黒になるようなケースは amount100% と指定した場合のみとなる。

ということで、Stylusの lighten() は元の色の輝度に対して割合的処理をしている、といえる。

タイトルの例について改めて触れると、

lighten.styl(再掲)
*
  color: lighten(red, 50%)
  // => color: #ff8080;

HSL色空間では red 等の原色はその時点で既に50%の輝度を持つため、Stylusの lighten(red, 50%) で「輝度50%から100%までの道のりを50%進む」ようにされた結果、輝度75%の #ff8080 が得られる。

お仲間の関数について

暗くする darken() については既に述べた。

彩度を変える saturate()desaturate() については、少し状況が異なる。
上記コア部分の 'l' == prop && val > 0 という場合分けの左辺(?)が示しているように、彩度関数では入力値が正の領域でも負の方と同じ1次関数が使われているため、amount100% と指定してもせいぜい元の2倍の彩度にしかならない。
この仕様の意図は正直よくわからない。まぁそういうもんなんだろう。

See the Pen Stylus HSL by 森の子リスのミーコの大冒険 (@phroneris) on CodePen.

(もしくはこちらから→ Try Stylus!

「ご覧の通り、下げる方向なら輝度でも彩度でも同じなのに上げる方向では違うよね」…ってやろうとしたんだけど、下げる方向もそれはそれで意味わからん誤差があるな……。
といってもパーセント値の一の位で四捨五入すれば同じだし、この程度なら許容されてるのかな…(なお、rate = 43% にすると四捨五入ですら不一致になる)。

あとなんかこんな挙動もある→ Try Stylus!

どちらが良いのか

良いも悪いも無い。
無いが、どちらかのCSSプリプロセッサを主に使いながらもう片方のものにも多少首を突っ込むような使い方をする人の場合、そういう差があるということだけは知っておく必要がある。
…と思う。

一応の比較考察

Sass側のポイント加算方式は、正直言って私達が「割合」の操作に期待するものではないところがある。
何せSassでは「#FF0000を半分くらい明るくしたい」と思って lighten(red, 50%) と書いたら #FFFFFF が返ってくるわけで、そりゃ公式ドキュメントにも警告が載るわなという感じがする。

ただ一方で、「サイコロを6回振ったら1回は6が出るはず」とか「ハサミギロチン2を3回撃ったら命中率90%」とか思ってしまうのもまた人情であり、ポイント加算方式はそういう思考にはむしろマッチするのが面白い。
あと、HSL色空間は

  • 色相(Hue):0~360 [°]
  • 彩度(Saturation):0~100 [%]
  • 輝度(Lightness):0~100 [%]

という定義上、彩度と輝度は元々がパーセント値なわけで、HSL色空間についてそこまで把握している人なら「そもそもパーセント値を更に割合操作する方がおかしい」と感じるところがあっても不思議ではないと思う。

じゃあどっちも使おうぜ!

違いはわかった。原理もわかった。
それならいっそ、両言語にある独自関数作成機能で、お相手のそれらに相当する関数を自作してしまおうじゃあないか。
そうすれば、言語を問わずいつでも好きな方を選んで使えるようになる。最強。

以下、各CodePenの右側のResult画面が、どちらの言語の例でもおおよそ同じ結果になるのを確認されたし。
誤差は仕方無し。

SassでStylusの lighten() を再現

概訳時にこっそり省いていたが、公式ドキュメントの lighten() 等の注意書きには、割合的な変更の実現方法として scale-color() が紹介されている。

https://sass-lang.com/documentation/modules/color#scale

color.scale($color,
  $red: null, $green: null, $blue: null,
  $saturation: null, $lightness: null,
  $alpha: null)
scale-color(...) //=> color 

$color の1つ以上の成分を流動的に拡縮します。

使わない手は無い。

See the Pen Sass HSL Control: vs Stylus by 森の子リスのミーコの大冒険 (@phroneris) on CodePen.

(もしくはこちらから→ SassMeister

StylusでSassの lighten() を再現

単純な足し引きなので楽ちん。

See the Pen Stylus HSL Control: vs Sass by 森の子リスのミーコの大冒険 (@phroneris) on CodePen.

(もしくはこちらから→ Try Stylus!

既知の誤差として、Kirakiratterのボタンの色を再現すべく sass-lighten(sass-desaturate(#6a4643, 5%), 12%) とすると、現物は #8a6461 なのにこの自作関数では #8a6460 になる。

まとめ

SassとStylusという、どちらも同じCSSプリプロセッサの役目を担う言語の、同じ名前の関数であり、「HSL色空間に準じた輝度を増やす」という同じような機能を持つ、lighten(color, amount)
しかし、その輝度をどのように増やすかという点で、両言語のそれには決定的な違いがある。

:space_invader: Sass Stylus
:computer: 処理 元の輝度に単純に加算 元の輝度から割合的に増加
:smile: 利点 元の輝度を知っていれば
計算結果が明白
真っ白になるのは
amount = 100% の時だけ
:thinking: 欠点 元の輝度を知らない場合は
予想外に白飛びする
割合の割合を操作する
という疑問が残る
:art: lighten(red, 50%)
white
lighten(red, 50%)
#ff8080

また、輝度を下げる darken()、彩度を上げる saturate()、彩度を下げる desaturate() も同様。
ただし、Stylusの saturate() だけはまた少し違う仕様を持つので注意。

そして、頑張ればどちらも自作関数で再現することができる。
ただし、どうしても細かい誤差が出る場合もあり、完全な互換性は望むべくもないので強く生きる。

余談

なんでこんなことを気にして調べるに至ったかというと、Stylus(ブラウザ拡張機能の方)で使えるUserCSSとして、マストドンの見た目をカスタムするものを作っている過程で気付いたため。

私はもっぱらStylus使い3だが、マストドンにはSass(SCSS)が採用されており、両者は記法とか変数とか関数とかが似ていたり違ったりする部分が当然色々ある。
そういう色々について色々格闘していた中で、特に色調整関数の挙動の違いにはかなり困惑し、またそれについて紹介する既存の資料も見当たらなかったので、自分自身のためも兼ねてここにまとめたのであった。

おわり


  1. 正確に言うと、HSL色空間には円柱モデルと双円錐モデルがあり、輝度はどちらでも同じだが彩度は計算が異なる。私の調べた限りではStylusは円柱モデルなので、まぁ多分Sassもそうでしょ(怠惰) 

  2. ポケモンの「一撃必殺技」で、命中率は30%と低いが、当たれば相手を即KOするロマン技。 

  3. Sassはマジで今回初めて触ったので、「SassでStylusの lighten() を再現」の節は苦労した。変なとこがあったら教えてください。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

永遠に続くクソ計算クイズ2 typescriptで

成果物

https://yuzuru2.github.io/neta1/dist/

リポジトリ

https://github.com/yuzuru2/yuzuru2.github.io/tree/master/neta1

UI

無題.png

src/index.ts

// くそIEに対応させる
import 'babel-polyfill';

class Calculation {
  private static instance: Calculation;

  // 秒数
  private count = 15;

  // タイマーで使う
  private time_id;

  // シングルトン
  private constructor() {}

  // dom
  private readonly id_left = document.getElementById('left');
  private readonly id_sign = document.getElementById('sign');
  private readonly id_right = document.getElementById('right');
  private readonly id_ans = document.getElementById('ans');
  private readonly id_time = document.getElementById('time');
  private readonly id_yes = document.getElementById('yes');
  private readonly id_no = document.getElementById('no');

  // 問題セット
  private set_question(left, sign, right) {
    this.id_left.textContent = left;
    this.id_sign.textContent = sign;
    this.id_right.textContent = right;
  }

  // 答えセット
  private set_answer(ans: string) {
    this.id_ans.textContent = ans;
  }

  // 足し算
  private sum(ans: boolean) {
    const _left = Math.floor(Math.random() * 100) + 1;
    const _right = Math.floor(Math.random() * 100) + 1;

    this.set_question(_left, '+', _right);

    if (ans) {
      this.set_answer(`${_left + _right}`);
      return;
    }

    // 不正解作成パターン1
    if (Math.floor(Math.random() * 2) === 0) {
      this.set_answer(
        `${_left + _right + (Math.floor(Math.random() * 3) + 1)}`
      );
    } else {
      // 不正解作成パターン2
      this.set_answer(
        `${_left + _right - (Math.floor(Math.random() * 3) + 1)}`
      );
    }
  }

  // 引き算
  private minus(ans: boolean) {
    const _left = Math.floor(Math.random() * 100) + 1;
    const _right = Math.floor(Math.random() * 100) + 1;

    this.set_question(_left, '-', _right);

    if (ans) {
      this.set_answer(`${_left - _right}`);
      return;
    }

    // 不正解作成パターン1
    if (Math.floor(Math.random() * 2) === 0) {
      this.set_answer(
        `${_left - _right + (Math.floor(Math.random() * 3) + 1)}`
      );
    } else {
      // 不正解作成パターン2
      this.set_answer(
        `${_left - _right - (Math.floor(Math.random() * 3) + 1)}`
      );
    }
  }

  // 掛け算
  private multiplication(ans: boolean) {
    const _left = Math.floor(Math.random() * 10) + 1;
    const _right = Math.floor(Math.random() * 10) + 1;

    this.set_question(_left, '×', _right);

    if (ans) {
      this.set_answer(`${_left * _right}`);
      return;
    }

    // 不正解作成パターン1
    if (Math.floor(Math.random() * 2) === 0) {
      this.set_answer(
        `${_left * _right + (Math.floor(Math.random() * 3) + 1)}`
      );
    } else {
      // 不正解作成パターン2
      this.set_answer(
        `${_left * _right - (Math.floor(Math.random() * 3) + 1)}`
      );
    }
  }

  // 割り算
  private division(ans: boolean) {
    let _left;
    let _right;

    while (1) {
      _left = Math.floor(Math.random() * 100) + 1;
      _right = Math.floor(Math.random() * 10) + 1;
      if (_left % _right === 0) {
        break;
      }
    }

    this.set_question(_left, '÷', _right);

    if (ans) {
      this.set_answer(`${_left / _right}`);
      return;
    }

    // 不正解作成パターン1
    if (Math.floor(Math.random() * 2) === 0) {
      this.set_answer(
        `${_left / _right + (Math.floor(Math.random() * 3) + 1)}`
      );
    } else {
      // 不正解作成パターン2
      this.set_answer(
        `${_left / _right - (Math.floor(Math.random() * 3) + 1)}`
      );
    }
  }

  // リセット
  private reset() {
    // タイマー処理
    const time_method = () => {
      this.id_time.textContent = `${--this.count}`;

      if (this.count === 0) {
        alert('タイムアップ');
        this.reset();
        return;
      }
      this.time_id = setTimeout(time_method, 1000);
    };

    clearTimeout(this.time_id);

    this.count = 15;
    this.time_id = setTimeout(time_method, 1000);

    // 時間セット
    this.id_time.textContent = `${this.count}`;

    // 正解・不正解どちらの問題を出すか決める
    const ans = Math.floor(Math.random() * 2) === 0;

    // 計算種類
    const _sign = Math.floor(Math.random() * 4);
    switch (_sign) {
      case 0:
        this.sum(ans);
        break;

      case 1:
        this.minus(ans);
        break;

      case 2:
        this.multiplication(ans);
        break;

      case 3:
        this.division(ans);
        break;

      default:
        break;
    }

    // yesボタンを押したとき
    this.id_yes.onclick = () => {
      ans ? alert('正解') : alert('不正解');
      this.reset();
    };

    // noボタンを押したとき
    this.id_no.onclick = () => {
      ans ? alert('不正解') : alert('正解');
      this.reset();
    };
  }

  // シングルトン 単一のインスタンスを返す
  public static get_instance(): Calculation {
    if (!this.instance) {
      this.instance = new Calculation();
    }

    // 生成済みのインスタンスを返す
    return this.instance;
  }

  // クイズをスタートする
  public start() {
    this.reset();
  }
}

Calculation.get_instance().start();

見返してみると、とんでもねーくそコードだな

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CSS Modules の問題点をミックスインで解決する

はじめまして、よこけんです。(2回目)
今日は、CSS Modules の問題点を解決する方法について検討したのでそれを共有してみます。

CSS Modules の問題点

CSS Modules ではクラスセレクタによるスタイル適用が基本です。
そしてクラス名は一つのコンポーネントに複数指定することができます。
しかし、複数指定した場合の適用順序は保証されません。クラス名の指定順序ではなく、クラスセレクタが読み込まれた順序に依存します。

例えば下記の場合、背景色は赤色ではなく青色になります。

a.styl
.a
    background-color: red
b.styl
.b
    background-color: blue
Hoge.tsx
import * as aStyles from "./a.styl";
import * as bStyles from "./b.styl";
const Hoge = () => <div className={`${bStyles.b} ${aStyles.a}`}>Hoge</div>;

サンプルコードでは Hoge.tsx がインポート順序を b.styl -> a.styl に変えれば意図した結果を得ることができますが、例えば Hoge.tsx から参照される別のコンポーネントで b.styl をインポートしている場合にはやはり .b が優先されて青色になってしまいます。
この問題は非常に厄介な上に、直接的な解決方法というものはありません。

ミックスインによる解決

本記事では、この問題に対する比較的扱いやすい解決方法として、Stylus や Sass のミックスイン機能を利用した解決方法を紹介します。
ミックスインはクラス継承と違い、適用位置にプロパティを全てコピーします。全てのプロパティが集約されることにより、単純にミックスインの指定順序に従ってプロパティが適用されます。ミックスイン本体の読み込み順序は関係ありません。

次のサンプルコードでは、mixHoge()mixB() の後に mixA() を呼んでいるため、背景色は必ず赤色になります。

a.styl
mixA()
    background-color: red

.a
    mixA()
b.styl
mixB()
    background-color: blue

.b
    mixB()
Hoge.styl
@import "a.styl"
@import "b.styl"

mixHoge()
    mixB()
    mixA()

.hoge
    mixHoge()
Hoge.tsx
import * as styles from "./Hoge.styl";
const Hoge = () => <div className={styles.hoge}>Hoge</div>;

シンプルなルール

前述のコードをルール化すると次のようになります。

  • 一つのコンポーネントに複数のクラスを指定してはいけない
  • クラスセレクタを用意する場合、対になる単一のミックスインを必ず用意する
  • クラスセレクタでは常に、対になる単一のミックスインの適用のみを行い、スタイル記述はミックスイン内で行う
  • クラス継承 (@extends) は一律禁止とし、代わりにミックスインを使用する

クラス継承 (@extends) は一律禁止ということに気を付けてください。
単一継承であれば大丈夫のように思うかもしれませんが、クラス継承を使ってしまうと、そこから先をミックスインで派生させても全てのプロパティが一箇所に集約されなくなってしまい、読み込み順序に再び依存するようになってしまいます。

この方式の欠点は、トランスパイルされた CSS ファイルのサイズが肥大化するリスクです。
前述の通り、ミックスインはクラス継承と違い、適用位置にプロパティを全てコピーします。だから読み込み順序に一切依存しなくなるわけですが、これはトランスパイル後の CSS ファイルのサイズに影響します。
スタイルの継承を多用するようなプロジェクトではリスクが顕在化するかもしれません。
リスクが顕在化してきた場合には、プロパティ数が多く継承も多く行われる特定のスタイルに対してのみクラス継承 (@extends) を許可し、それらのクラスセレクタだけは読み込み順序を慎重に管理します。

追加ルールでリスクを軽減

先ほどはクラス継承を使用すると問題に繋がるとしていましたが、厳密には、派生を許可しないクラスからであればクラス継承を使用しても問題には繋がりません。
合法的にクラス継承を使用できるケースが発生すると、ファイルサイズを抑える効果が期待できます。

次のサンプルコードは、.hoge クラスの派生を禁止することで安全を確保できます。

a.styl
mixA()
    background-color: red

.a
    mixA()
b.styl
mixB()
    background-color: blue

.b
    mixB()
Hoge.styl
@import "a.styl"
@import "b.styl"

.hoge
    @extends .b
    mixA()
Hoge.tsx
import * as styles from "./Hoge.styl";
const Hoge = () => <div className={styles.hoge}>Hoge</div>;

これをルール化すると次のようになります。

  • 一つのコンポーネントに複数のクラスを指定してはいけない
  • 派生を許可するクラスセレクタを用意する場合、対になる単一のミックスインを必ず用意する
  • 派生を許可するクラスセレクタでは常に対になる単一のミックスインの適用のみを行い、スタイル記述はミックスイン内で行う
  • ミックスイン内でのクラス継承 (@extends) は一律禁止とし、代わりにミックスインを使用する
  • 派生を許可しないクラスセレクタではスタイルを直接記述して良い
  • 派生を許可しないクラスセレクタではクラスを一つだけ継承 (@extends) して良い (2つ以上のクラスを継承したい場合はミックスインを併用する)

この方式の欠点は二つあります。

一つはルールが少し複雑になるために混乱を招いたりルール違反が発生しやすくなることです。
ただし、派生を許可するクラスセレクタがあまり多くない (整理されていて見通しが良い) プロジェクトなら、追加ルールを適用しても混乱やルール違反は最低限に抑えられると思います。

もう一つは、リスクの低減はできても完全に回避することはできないということです。
リスクが顕在化してきたら最初の解決方法と同様、特定のスタイルに対してクラス継承 (@extends) を許可し、それらのクラスセレクタだけは読み込み順序を慎重に管理します。
しかし、元々少し複雑なルールにこの例外措置が加わることになりますので、混乱やルール違反をより招きやすくなる恐れがあります。

結論

  • リスクが顕在化する可能性が低そうであればシンプルなルールを採用する
  • 追加ルールを採用する場合、混乱やルール違反を招かないよう工夫する

なお、そもそも複数クラスの継承をしようとしなければ問題は起きません。ただし、そのためのルールは結局必要になります。 (そして恐らく、そのルールによって新たなリスクも発生します。)

根本的には、クラスセレクタの読み込み順序ではなくクラス指定順序で結果が決まってくれれば良いんですが、CSS の仕様のようなので。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む