20191204のCSSに関する記事は9件です。

WordpressでCSSにbackground-imageに指定した画像が表示されない問題

物凄くくだらないことだったが意外と答えにたどり着くまでに時間がかかったので同じような境遇の人はいないと思うがメモ

前提としてフォルダ構成、パスはこんな感じ。

wp-content/themes/sample_theme

sample_theme
 index.php
 style.css
 images
  sample.jpg

HTMLのimgタグのsrc属性を指定する際はこんなwordpressのタグをつけると、テンプレートディレクトリを出力してくれる。
※階層がテーマフォルダ/img/画像ファイルの場合

index.php
<img src="<?php bloginfo('template_directory'); ?>/images/samole.jpg" alt="sample">

しかし、CSSファイルにて,純粋なフォルダパスを指定しても画像を読み込んでくれない。
上記に倣って下のように<?php bloginfo('template_directory'); ?>を使用してもこれまたうまくいかない。
調べてもうまくいかず地味に悩んだ。

style.css
/*ダメ*/
background-image: url("<?php bloginfo('template_directory'); ?>/images/sample.jpg");

wordpress上でstyle.css以外のCSSを読み込むために使う<?php echo get_template_directory_uri(); ?>でもダメ。

style.css
/*ダメ*/
background-image: url("<?php echo get_template_directory_uri(); ?>/images/sample.jpg");

wordpressの特殊なタグは必要なく、テンプレートフォルダの直下にあるimageフォルダを直接指定するだけで読み込んでくれた。

style.css
/*OK!!*/
background-image: url("images/sample.jpg");
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

onsenuiを利用してサイドバーを設置しましたが、iframeでMapを表示させるとサイドバーが閉じない

前提・実現したいこと

Monacaを利用して、Onsenui+javascriptにてアプリを制作しています。
サイドバーをで作成して、その中のアイテムを選択すると、googlemapをiframeで表示したいと考えいております。

発生している問題・エラーメッセージ

サイドバー単体では動作は問題ないのですが、iframeを使用するとmap表示がされなくなります。
iframeを優先させるとサイドバーが閉じなくなります。

該当のソースコード

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
  <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: https://ssl.gstatic.com; style-src * 'unsafe-inline'; script-src * 'unsafe-inline' 'unsafe-eval'">
  <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">
  <link rel="stylesheet" href="css/style.css">

<script>
    window.fn = {};

    //初期読み込み時のマップ表示
    window.onload = function() {
      "use strict";
      var url = "https://maps.google.co.jp/maps";
      var param = {
          tokyo: {
            center: { lat: 35.681661, lng: 139.767185 }
          },
          zoom: 18,
          lang: "ja"
        };
      var name = "tokyo"
      var iframe = document.getElementById("ifr");
      // パラメータ指定
      var p = "?output=embed"
            + "&ll=" + param[name].center.lat.toString() + "," + param[name].center.lng.toString()
            + "&z=" + param.zoom.toString()
            + "&hl=" + param.lang;
      iframe.setAttribute("src", url + p);
    }

    window.fn.open = function() {
      var menu = document.getElementById('menu');
      menu.open();
    };

    window.fn.load = function(page,name) {
      "use strict";
      var url = "https://maps.google.co.jp/maps";
      var param = {
          tokyo: {
            center: { lat: 35.681661, lng: 139.767185 }
          },
          oosaka: {
            center: { lat: 34.702803, lng: 135.495908 }
          },
          zoom: 18,
          lang: "ja"
        };

      var content = document.getElementById('content');
      var menu = document.getElementById('menu');
      var iframe = document.getElementById("ifr");
      // パラメータ指定
      var p = "?output=embed"
            + "&ll=" + param[name].center.lat.toString() + "," + param[name].center.lng.toString()
            + "&z=" + param.zoom.toString()
            + "&hl=" + param.lang;
      iframe.setAttribute("src", url + p);
      //alert(url + p);

      content.load(page)
        .then(menu.close.bind(menu));
    };
</script>
</head>
<body>
  <ons-page>
    <ons-toolbar>
      <div class="center">Map</div>
    </ons-toolbar>
<ons-splitter>
  <ons-splitter-side id="menu" side="left" width="220px" collapse swipeable>
    <ons-page>
      <ons-list>
        <ons-list-item onclick="fn.load('map1.html','tokyo')" tappable>
          東京駅
        </ons-list-item>
        <ons-list-item onclick="fn.load('map2.html','oosaka')" tappable>
          大阪駅
        </ons-list-item>
      </ons-list>
    </ons-page>
  </ons-splitter-side>
  <ons-splitter-content id="content" page="map1.html"></ons-splitter-content>
</ons-splitter>

<template id="map1.html">
  <ons-page>
    <ons-toolbar>
      <div class="left">
        <ons-toolbar-button onclick="fn.open()">
          <ons-icon icon="md-menu"></ons-icon>
        </ons-toolbar-button>
      </div>
      <div class="center">
        東京駅
      </div>
    </ons-toolbar>
    <iframe id="ifr" width="100%" height="100%"></iframe>
  </ons-page>
</template>

<template id="map2.html">
  <ons-page>
    <ons-toolbar>
      <div class="left">
        <ons-toolbar-button onclick="fn.open()">
          <ons-icon icon="md-menu"></ons-icon>
        </ons-toolbar-button>
      </div>
      <div class="center">
        大阪駅
      </div>
    </ons-toolbar>
    <iframe id="ifr" width="100%" height="100%"></iframe>
  </ons-page>
</template>
  </ons-page>
</body>
</html>

試したこと

iframeのsrcに入れるデータをalertで表示させてみましたが、ちゃんと関数内で受け取れていました。

  content.load(page)
    .then(menu.close.bind(menu));

javascript内のcontent.loadのタイミングが一番悪さをしていそうですが、どう修正していいかわかりません。

よい方法を教えていただけますでしょうか?
お願い致します。

使用

フレームワーク:onsenui
言語:HTML+CSS+javascript

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

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

100日チャレンジの176日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。

100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。

176日目は

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

CSSでWebサイトをダークモード対応させよう

はじめに

この記事は NTTテクノクロス Advent Calendar 2019 の6日目の記事です。

こんにちは。NTTテクノクロスの鈴木雅貴です。当社製品やソリューション、受注案件を中心とした成果物をはじめとして、イベントやらコミュニケーションといった社内のあれこれを、デザインの力でよくしていくというチームのリーダをしています。個人としてはWeb標準技術まわりのスキルで生きており、HTML5プロフェッショナル認定試験 レベル1 対策テキスト&問題集 Ver2.0対応版のいち著者だったりします。と見せかけてプロジェクト管理をテーマにしたボードゲーム『プロジェクト テーマパーク』を遊んでみた | NTTテクノクロスブログのように、隙あらば遊ぼうとしています。

今回は、iOS 13やAndroid 10でも使えるようになり、利用者が増えてきたダークモード(ダークテーマとも言われますが、本記事ではダークモードに統一します)について、Webサイトでの対応方法を紹介します。

ダークモードとは

ダークモードとは、画面の配色を黒基調のものに切り替えられる機能であったり、状態のことを指します。最近はiOSやAndroidだけでなく、Windows 10やMac OS Mojaveでもダークモードに相当するものが実装され、OSレベルで黒基調の配色を選択できるようになりました。

ダークモードのメリットとしてよく挙げられるのは以下です。

  • 画面の輝度が下がるので、目に優しい。
  • 画面の照度が下がるので、バッテリーが長持ちする。特に自発光するOLEDでは効果が大きいと言われる。
  • なんだかクールでカッコいい!

そのような理由で、利用者が増えているようです。

ただ、そのダークモードの中で白い背景のウィンドウが登場すると、眩しくてちとつらいわけです。特にWebサイト。白背景が多い。しかしWebサイト側で、OSのモードに合わせて白だの黒だのやるわけにはいかないわけで…

と思いきや、できるようになっているんです。しかもCSSで。

CSSでのダークモード対応

CSSの機能に、Webブラウザの状態に応じてCSSを切り替えることができる、メディアクエリなるものがあります。画面幅に応じて見た目を変更したいときなどによく使われていますね。

そのメディアクエリでOSのテーマに対応できるよう、現在策定中のCSS仕様であるMedia Queries Level 5には、prefers-color-schemeなるものが盛り込まれています。

https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme (参考日本語訳)

これを使えば、ダークモード対応はとても簡単。従来のメディアクエリ同様、darkの場合の色設定をCSSで書くだけです。

例えば、ダークモード使用時、Webサイト背景は黒で文字は白で表示させたい場合、CSSで以下のようにすればオッケーです。

@media (prefers-color-scheme: dark) {
  body{
    background-color: #000;
    color: #fff;
  }
}

perfers-color-scheme: darkがダークモード使用時という条件です。その場合のCSS指定を書いてあげるだけです。

やることはこれだけ!

策定中ということで、気になるWebブラウザの対応状況ですが、おなじみCan I useによると、2019年12月現在、いわゆるモダンブラウザではEdgeを除き実装されているようです。Edgeも2020.01.15リリース予定のChronium版からは使えるようになるはず。もうすぐですね!

もちろん色以外も変更できます

これを使えば、色以外のものも変更できます。CSSですから。

たとえば画像。picture要素内に配置したsource要素でmedia属性を使えば、モードにあわせて違う画像を表示できます。下の例では、普段はlight.png、ダークモードではdark.pngが表示されます。picture要素についてはMDNをご覧ください。

<picture>
  <source srcset="dark.png" media="(prefers-color-scheme: dark)">
  <img src="light.png">
</picture>

せっかくなのでメンテしやすいように

普段(いわゆるライトモードを想定)は白背景、黒文字、ダークモードは逆になるようなCSSを書くとなるとこんな感じになるでしょう。ライトモードではlightを指定します。

@media (prefers-color-scheme: light) {
  body {
    background-color: #fff;
    color: #000;
  }
}

@media (prefers-color-scheme: dark) {
  body {
    background-color: #000;
    color: #fff;
  }
}

モードによる変更点がこの程度ならよいのですが、たいていもっと多いはず。メンテナンスは大変になるでしょう。また、新しいモードが登場した場合、また同じようなことを書くのも面倒ですよね。そこで、CSSで変数のように使えるCSS Custom Propertiesを使って変更点をまとめておくのがオススメです。CSS Custom PropertiesについてはMDNをご覧ください。

使用例はこんな感じです。

@media (prefers-color-scheme: light) {
  :root {
    --bgcolor: #fff;
    --textcolor: #000;
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --bgcolor: #000;
    --textcolor: #fff;
  }
}

body {
  background-color: var(--bgcolor);
  color: var(--textcolor);
}

この例では記述量が増えただけのように見えますが、本来のCSS記述箇所は1つだけになるので、ずいぶん読みやすくなるはずです。

JavaScriptからアクセスしてみよう

JavaScriptからモードを取得したい場合は、従来のメディアクエリ同様にwindow.matchMedia()を使えばOKです。window.matchMedia()については、MDNをご覧ください。

// ダークモードかどうかを取得
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

モード変更時のイベントハンドラを使えば、ダークモードに変更されたときの処理を書くこともできます。

window.matchMedia('(prefers-color-scheme: dark)')
  .addListener((e) => {
    if (e.matches) {
      // ダークモード時の処理
    } else {
      // それ以外のモードの処理
    }
  });

こちらも、今までのメディアクエリ同様に書くことができます。

ダークモードで気をつけること

メディアクエリとして実装されたおかげで、ダークモード対応は技術的には難しくありません。しかし、作られるサイト側では気をつけたいポイントがいくつかありますので、それを紹介します。

背景色は真っ黒にしない

上の例で使っておきながらなんですが、ダークモードでの背景色に真っ黒の#000を使うことはおすすめしません。真っ黒は、自然界に存在しないので、見ると違和感を感じやすいです。特に制限がなければ、少し白方向に倒しておくのがよいでしょう。

また、ディスプレイがOLEDだと、真っ黒の#000はディスプレイが発光していない状態です。そこから違う色へ変わるためには、ディスプレイを発光させなければいけません。ディスプレイの発光は、発光したままの色変更と比較すると時間がかかるため、それがスクロール時ににじみのような違和感となることがあります。真っ黒は省エネではあるのですが、こういう現象が発生しうることを認識しておくとよいでしょう。

画像の彩度を落とす

暗い背景では、明るい背景と比較して画像が明るく見えます。同じ画像でも違う印象になりがちなので、気になるようなら画像の彩度を落としましょう。

ざっくり対応して楽をしたい場合は、ダークモード時、以下のように、CSSのfilterプロパティでgrayscale()を指定するのもよいでしょう。

img {
  filter: grayscale(50%);
}

真っ黒と真っ白の組み合わせは目が疲れやすい

#000#fffの組み合わせは、コントラスト比が一番高くなります。Web Content Accessibility Guidelines (WCAG)(日本語訳)的な観点では「コントラスト比はxx以上にしましょう」というチェックだけのため、それで問題ないように思えます。しかし、コントラスト比が大きすぎると光量差が大きくなるため、目が疲れやすくなると言われています(暗闇の中の明るい光のイメージですね)。ですので、その観点からも、背景色に真っ黒を使うのは避けたほうがよいでしょう。

おわりに

今回は、CSSを使ったWebサイトのダークモード対応を紹介し、あわせて気をつけたほうがよいポイントを紹介してみました。

ダークモード対応して、Coolそして悪目立ちしないステキなサイトをつくりましょう!

7日目は @kokubo_yuichi さんです。お楽しみに!

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

User JavaScript and CSS (Chrome 拡張機能) を使って Redmine をカスタマイズしてみた

User JavaScript and CSS とは

Chrome の拡張機能として提供されている,既存のウェブサイトに独自の Javascript や CSS を埋め込むライブラリです.
User JavaScript and CSS - Chrome ウェブストア

これを利用して,ダークモードがないウェブサイトにダークモードを実装してみたり, Yahoo! のサイトに自分の好きなキャラクターのイラストを表示させたり,いろいろなことができます.
今回は,社内のタスク管理に使用している Redmine で,チケットの説明に出てくるスニペットの内容をクリップボードにコピーする機能を Javascript で実装してみました.

画面設計

スニペットの枠外,右下部分にコピーのアイコンを配置し,

copy.png

アイコンをクリックすると「コピーしました!」のダイアログを表示することにします.

copied.png

実装

アイコンを読み込む

今回は Font Awesome のアイコンを使用します.
Font Awesome についてはこちら

流れは次の通りです.

  • Font Awesome を読み込む link タグを作成する
  • head タグを取得し,その子要素として先ほど作成した link タグを追加する
redmine.js
const link = document.createElement('link');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('href', 'https://use.fontawesome.com/releases/v5.6.3/css/all.css');

document.getElementsByTagName('head')[0].appendChild(link);

スニペットの枠外,右下部分に要素を配置する

流れは次の通りです.

スニペット (pre タグ) を取得する -> 各スニペットが配列で取れます

各スニペットに対して,次の処理を行う

  • コピーボタンを配置するための p タグを作成する (snippet)
  • 後にコピーボタンとなる span タグを作成する (copyButton)
  • (copyButton に title, innerHTML, className などの属性を設定する)
  • copyButton にクリックのイベントリスナーを付与する (内容は下記)
    • コピー対象となる textarea タグを作成 (textarea タグでなくても行けるかもしれません)
    • スニペット内の文章を textarea タグに設定する
    • 作成した textarea タグを body タグの子要素として配置 -> 選択 -> コピー
    • textarea タグは不要になるので取り除く
    • コピーが完了した旨のメッセージを表示
  • copyButton を snippet の直後に配置する
redmine.js
// スニペットのタグを選択
const snippets = document.getElementsByTagName('pre');
// console.log(snippets); // HTMLCollection
// console.log(typeof snippets); // object

// コピーボタンを配置
Object.keys(snippets).forEach(function (index) {
    let snippet = snippets[index];
    let p = document.createElement('p');
    p.className = 'copy-snippet';

    let copyButton = document.createElement('span');
    copyButton.title = 'Copy to Clipboard';
    copyButton.innerHTML = '<i class="far fa-copy"></i> Copy';
    copyButton.className = 'copy-button';

    copyButton.addEventListener('click', function () {
        let copyTarget = document.createElement('textarea');
        copyTarget.textContent = snippet.innerText.trim(); // 見た目に合わせて文末改行削る?
        let body = document.getElementsByTagName('body')[0];
        body.appendChild(copyTarget);
        copyTarget.select();
        document.execCommand("copy");
        body.removeChild(copyTarget);
        // 後処理まで終わったらおまけ
        (function () {
            alert('コピーしました!');
        })();
    });
    p.appendChild(copyButton);
    snippet.parentNode.insertBefore(p, snippet.nextSibling);
});

その他

画面右下にカエルのイラストを入れて,全体を CSS で整えます.

redmine.js
// カエルがペコペコおじぎする GIF
const imageUrl = 'https://sozai-good.com/download?id=2451&type=7&subnumber=0&extention=gif';

// 画像挿入処理
let img = document.createElement('img');
img.id = 'inserted-image';
img.height = 105;
img.src = imageUrl;
const wrapper = document.getElementById('wrapper');
wrapper.parentNode.insertBefore(img, wrapper.nextSibling);
redmine.css
#wrapper, #main {
    background-image: url("https://www.pakutaso.com/shared/img/thumb/mizuho1102dssd_TP_V.jpg");
    background-repeat: no-repeat;
    background-size: cover;
    background-attachment: fixed;
    opacity: 0.96;
}

#top-menu {
    /*background-color: darkgreen;*/
    background-color: rgba(10, 106, 10, 0.9);
}

#header, #footer {
    /*background-color: forestgreen;*/
    background-color: rgba(0, 0, 0, 0.5);
}

#main-menu li a {
    /*background-color: darkgreen;*/
    background-color: rgba(10, 106, 10, 0.6);
}

#main-menu li .selected {
    color: lightgreen;
}

#content {
    margin-bottom: 20px;
}

#sidebar {
    background-color: white;
}

#footer {
    position: fixed;
    background-color: black;
    right: 0;
    bottom: 0;
}

pre, .CodeRay {
    font-family: Monaco, monospace;
}

#inserted-image {
    position: fixed;
    right: 25px;
    bottom: 45px;
}

.copy-snippet {
    text-align: right;
}

.copy-button {
    cursor: pointer;
    background: none;
    border: 0;

    /* settings when hovered */
    /*transition: color 0.1s linear;*/
}

.copy-button:hover {
    color: red;
}

Redmine の公式ページを表示してみる

Defect #32525: CSV related tests fail with Rails 5.2.4 - Redmine

sample_page.png

やったー!

効用

  • 「コマンドはコピペして貼り付ける」のが暗黙のルールとなっている本番環境へのリリース作業において,逐一スニペットの中身をマウスで選択する手間が省けます.
  • 自分の画面だけに実装されるので,他の人に自慢できます.
  • フロント周りがちょっとだけ楽しくなります.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

困る前に知っておきたい SVGの基本再解釈

はじめに

アイコンやロゴなどで使用されることが多いSVGですが、よくわからずにとりあえずで使ってしまうと意外なところでハマってしまったりします。
SVGをソースレベルで解釈しつつ、現状で自分なりにベストな使用方法を探っていきます。

対象

SVGってあれでしょ?よく知らないけど

  • 拡大しても画像が綺麗なやつ
  • ちょっと使ってみたけど挙動がよくわからないやつ
  • ぱっと見でコードがごちゃごちゃしてて何やってるかよくわからないので、結局JPGとかPNGにしちゃうやつ

このくらいの人を対象に想定して書いています。
「アニメーションとかパーツごとにごりごり動かしたりはしないけど、CSSでアイコンの色を変えたりレスポンシブに対応させたりはしたいなー」くらいな感じで使用していくときの前提知識として読んでいただけると幸いです。

「SVGチョットデキル」という人はまとめまで飛んでいただくと良いかと思います。

そもそもSVGを使うメリットって?

以下のようなボタンコンポーネントを例に確認していきます。
Normal Raised Light@2x.png

拡大しても画像が綺麗

PNGやJPGはラスターグラフィックスであり、SVGはベクターグラフィックスであるというところがキーワードになります。
ピクセルごとに色を割り振るラスターグラフィックスに対し、ベクターグラフィックスは「どの座標からどこまで線を引く」というパスから描画を行うため、拡大してもピクセル単位の荒れが出ません。

  • PNG(ラスターグラフィックス)
    ラスターグラフィックス参考画像

  • SVG(ベクターグラフィックス)
    ベクターグラフィックス参考画像

データ容量が比較的小さい

SVGはパスと座標のデータが大部分のテキストデータですので、データ容量が比較的小さく済みます。またテキストエディターで開き、編集することも可能です。
アイコンやロゴなどは線や色の規則性がコードで表現しやすいため、SVGでの使用に向いていますが、色や線が複雑な写真などを表示するのには不向きであると言えます。

色などをCSSで制御できる

パスの中身を塗りつぶす色などを、WebページのCSSから制御することができます。
「SVGで置いたアイコンの背景色などによって画像の色を少し変えたい!」というときなど、わざわざ複数の画像を用意する必要がない訳ですね。

もう少し掘り下げておくと

規格としてはxml(拡張可能なマークアップ言語)をベースとしたテキストデータです。
整形したSVGデータを眺めてみるとHTMLの記述にも近いものがあり、大体の構造が見えてくるかと思います。
次節で基本的な記述方法を把握し、SVGデータを眺めていきます。

基本的なSVGデータの記述

<svg> 要素

SVGデータを記述するための外枠です。
ビューポートの大きさであるwidth, heightや、アスペクト比となるviewBoxも記述されます。

width, height, viewBox指定の有無で、SVGデータは以下のように描画されます。

  • width, heightが指定されている場合 縦横のサイズが固定されます。
  • viewboxのみが指定されている場合 縦横の比率は保たれますが、サイズが固定されていないのでレスポンシブに対応させることが可能です。
  • 全て指定されていない場合 基本的にはSVGのサイズのデフォルト値としてwidth="300px", height="150px"で表示されるようです。

詳細は以下をご覧ください。
HTMLタグリファレンス svg要素

<defs> 要素

defs要素内に定義された要素はそのままでは描画されません。
SVGデータ内で同じようなパスを使いまわしたい場合など、defs要素内で定義しておいてdefs要素外から参照する、というような使い方をします。
参照される要素は、可能なかぎりdefs要素内で定義されることが推奨されています。

詳細は以下をご覧ください。
HTMLタグリファレンス defs要素

<path> 要素

画像を描画するためのパスを記述します。
パスを記述する要素として、<rect> や <circle> など沢山もありますが、<path> 要素はこれらの元となる要素であり、他の描画要素は可読性の向上や記述量の削減のためにショートカットとして定義されているもののようです。
描画要素についてはキリがなさそうなので省略しますが、よく見かけるものとして以下のような要素があります。

<circle>
<ellipse>
<line>
<polygon>
<polyline>
<rect>

詳細は以下をご覧ください。
HTMLタグリファレンス path要素

<g> 要素

パスのグルーピングを行う要素です。HTMLでのdivタグのようなものだと思っておけば良さそうです。

詳細は以下をご覧ください。
HTMLタグリファレンス g要素

このくらいを把握しておけば、ソースを眺めたときにSVGの実態をざっくり掴むことができるかと思います。

SVGのソースを眺めてみる

Adobe XDでボタンコンポーネントをSVGとして書き出し、ソースを眺めてみます。
XDではSVGの書き出し時に内部CSSプレゼンテーション属性で選択が可能です。
それぞれ以下のように書き出しが行われます。

内部CSSでの書き出し (1325バイト)

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="36" viewBox="0 0 90 36">
  <defs>
    <style>
      .cls-1 {
        fill: none;
      }

      .cls-2 {
        clip-path: url(#clip-path);
      }

      .cls-3 {
        fill: #009688;
      }

      .cls-4 {
        fill: #fff;
        font-size: 14px;
        font-family: Roboto-Medium, Roboto;
        font-weight: 500;
      }

      .cls-5 {
        filter: url(#長方形_182);
      }
    </style>
    <clipPath id="clip-path">
      <rect class="cls-1" width="90" height="36"/>
    </clipPath>
    <filter id="長方形_182" x="-3" y="-1" width="96" height="42" filterUnits="userSpaceOnUse">
      <feOffset dy="2" input="SourceAlpha"/>
      <feGaussianBlur stdDeviation="1" result="blur"/>
      <feFlood flood-opacity="0.239"/>
      <feComposite operator="in" in2="blur"/>
      <feComposite in="SourceGraphic"/>
    </filter>
  </defs>
  <g id="Normal_Raised_Light" data-name="Normal Raised Light" class="cls-2">
    <g class="cls-5" transform="matrix(1, 0, 0, 1, 0, 0)">
      <rect id="長方形_182-2" data-name="長方形 182" class="cls-3" width="90" height="36" rx="2"/>
    </g>
    <text id="Normal" class="cls-4" transform="translate(16 23)"><tspan x="0" y="0">NORMAL</tspan></text>
  </g>
</svg>

内部CSSでの書き出しでは、<defs> で囲まれた <style> 要素内にCSSが記述され、グルーピングされたパスにスタイルが当てられていることが確認できます。

プレゼンテーション属性での書き出し (1045バイト)

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="36" viewBox="0 0 90 36">
  <defs>
    <clipPath id="clip-path">
      <rect width="90" height="36" fill="none"/>
    </clipPath>
    <filter id="長方形_182" x="-3" y="-1" width="96" height="42" filterUnits="userSpaceOnUse">
      <feOffset dy="2" input="SourceAlpha"/>
      <feGaussianBlur stdDeviation="1" result="blur"/>
      <feFlood flood-opacity="0.239"/>
      <feComposite operator="in" in2="blur"/>
      <feComposite in="SourceGraphic"/>
    </filter>
  </defs>
  <g id="Normal_Raised_Light" data-name="Normal Raised Light" clip-path="url(#clip-path)">
    <g transform="matrix(1, 0, 0, 1, 0, 0)" filter="url(#長方形_182)">
      <rect id="長方形_182-2" data-name="長方形 182" width="90" height="36" rx="2" fill="#009688"/>
    </g>
    <text id="Normal" transform="translate(16 23)" fill="#fff" font-size="14" font-family="Roboto-Medium, Roboto" font-weight="500"><tspan x="0" y="0">NORMAL</tspan></text>
  </g>
</svg>

<text id="Normal" transform="translate(16 23)" fill="#fff" font-size="14" font-family="Roboto-Medium, Roboto" font-weight="500"><tspan x="0" y="0">NORMAL</tspan></text>`

プレゼンテーション属性では内部CSSは記述されず、各パスに属性としてスタイルが割り振られています。
可読性は内部CSSでの書き出しの方が高そうですが、今回のボタンコンポーネントに関してはプレゼンテーション属性の方が容量が小さいようですね。

Webでの扱い

WebページにSVGを表示させたいとき、HTMLにSVGデータを直書きする方法(インラインSVG)と、外部画像ファイルとしてimgタグでの読み込む方法があります。
こちらも簡単に比較してみます。

インラインSVG の場合

  • メリット

    • タグやクラスに個別にスタイルを当てることができる
    • SVGの機能を全て使用することができる
  • デメリット

    • HTMLに直書きすることになるので、コードが煩雑になり可読性が低下する
    • HTMLのファイルサイズが肥大化してしまう

外部画像ファイルとしてimgタグでの読み込み の場合

  • メリット

    • PNG画像やJPG画像と同じように扱うことができる(imgタグで拡張子を変えるだけ)ため、HTMLへの記述がコンパクトにまとまる
  • デメリット

    • パーツごとにスタイルを当てることはできない

SVGの取り扱い方法 個人的メモ

現状、ロゴなどでSVGを使用する場合においては以下の取り扱いをすると良いかと考えています。

使用時は外部画像ファイルとしてimgタグで読み込み

パーツごとのアニメーションなどが必要なければ、HTMLの見通しやSVGファイルごとの差し替えを考慮し、外部ファイルとして読み込んでおくのが良いかと思います。

書き出しはプレゼンテーション属性で行う

プレゼンテーション属性で記述されたスタイルは優先順位が低く、CSSなどでの上書きが容易なようです。
画像があまり複雑でないアイコンやロゴなどであれば、内部CSSでスタイルを切り離すよりもこちらの方が扱いやすいかと思います。

またSVGデータの扱いにおいて、複数のSVGデータを一つにまとめるSVGスプライトという方法が取られることがありますが、プレゼンテーション属性での記述であれば内部CSSの衝突を避けることができそうです。
(こちらは未確認です。)

svg要素に記述されているwidthとheightは削除

svg要素に記述されているwidthとheightは、画像のサイズを固定してしまいます。
レスポンシブに対応させるため、こちらの記述は削除しておきます。
画像のアスペクト比はviewBoxで指定しておき、WebページのCSS側で大きさを指定します。

注意点
width, heightを指定せずにbackgroundでSVGを読み込んだ場合、IEで表示崩れが起きることがあるようで、この場合に関してはwidth, heightは削除しない方が良さそうです。
imgタグで読み込む分には問題ないかと思っているのですが、他にも表示崩れがある場合、ご教授いただけると幸いです。

まとめ

現状このあたりを最低限意識しておくと、比較的ストレスなくSVGが使用できることかと思います。

  • 外部画像ファイルとしてimgタグで読み込み
  • 書き出しはプレゼンテーション属性で行う
  • svg要素に記述されているwidthとheightは削除

クリスマスまであと三週間ですね、皆様良い12月をお過ごしください!

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

条件によってテキストが変わるコンポーネントを分割して共通スタイルを適用する

こちらは、弁護士ドットコム Advent Calendar 2019 - Qiita の 21 日目の記事です。

要望

条件によって中身が変わるからコンポーネントは分けたいけど、スタイルは共通化したい。
JS によるロジックは特にない。

具体的なケース

ユーザーの権限によってテキストが変わるコンポーネント。

共通のスタイルを使用したいのですが、一つのコンポーネントにまとめようとすると v-if の嵐になってしまい、可読性がかなり落ちてしまいます。

イメージ
<p v-if="condition" class="style1">◯◯権限を持っているので、☓☓が出来ます。</p>
<p v-else class="style1">◯◯権限を持っていないので、☓☓が出来ません。</p>

<p v-if="condition" class="style2">△△権限を持っているので、□□が出来ます。</p>
<p v-else class="style2">ただし△△権限を持っていないので、□□が出来ません。</p>

<p class="style3">どのユーザーも☆☆は出来ます。</p> <!-- どの権限でも同じ -->

解決策

条件ごとにコンポーネントを分け、条件分岐・共通スタイル定義を親コンポーネントで行います。

スタイルを共通化させると、v-if をコンポーネントの切り替えの 1 つだけにできるので可読性が上がります。

実装

親コンポーネントにスタイルを持たせて、条件によって子コンポーネントを切り替えるようにします。

親コンポーネント
<template>
  <div class="wrapper">
    <component :is="componentName" v-bind="propData" />
  </div>
</template>

<script>
import Component1 from './Component1.vue'
import Component2 from './Component2.vue'

export default {
  components: {
    Component1,
    Component2
  },
  props: {
    condition: {
      type: String,
      required: true
    },
    username: {
      type: String,
      required: true
    }
  },
  data() {
    return {
      propData: {
        username: this.username
      }
    }
  },
  computed: {
    componentName() {
      switch (this.condition) {
        case 'cond1':
          return Component1
        case 'cond2':
          return Component2
        default:
          return null
      }
    }
  }
}
</script>

<style scoped>
.wrapper >>> .style1 {
  font-size: 20px;
}
.wrapper >>> .style2 {
  font-size: 16px;
}
.wrapper >>> .style3 {
  font-size: 16px;
  font-weight: bold;
}
</style>
Component1.vue
<template>
  <div>
    <h1>{{ username }}さん</h1>
    <p class="style1">閲覧権限を持っているので、プロパティの閲覧が出来ます。</p>
    <p class="style2">ただし編集権限を持っていないので、プロパティの編集が出来ません。</p>
    <p class="style3">どのユーザーもログインは出来ます。</p>
  </div>
</template>

<script>
export default {
  props: {
    username: {
      type: String,
      required: true
    }
  }
}
</script>
Component2.vue
<template>
  <div>
    <h1>{{ username }}さん</h1>
    <p class="style1">閲覧権限を持っているので、プロパティの閲覧が出来ます。</p>
    <p class="style2">編集権限を持っているので、プロパティの編集が出来ます。</p>
    <p class="style3">どのユーザーもログインは出来ます。</p>
  </div>
</template>

<script>
export default {
  props: {
    username: {
      type: String,
      required: true
    }
  }
}
</script>

注意

>>>(ディープセレクタ)は子孫要素すべてを対象とするので、scoped にしているからといって同じクラス名を使っているとスタイルがあたってしまいます。
コンポーネント名を prefix として付ける、BEM などの命名規則を適用する、などの対策が必要です。

また、SCSS 等を使用している場合は、>>> ではなく /deep/ を使用する必要があります。

他の選択肢

CSS を外部ファイル化して @import で読み込む

HTML, CSS, JavaScript が一緒に管理できる SFC の利点が消えてしまうので見送りました。
「全く違う場所で利用するコンポーネントだけどスタイルは共通化させたい」というときは Minxin 的に使えるかもしれません。

共通化しない

個々のコンポーネントとして取り扱えたほうがいいことが往々にしてあるので、選択肢としてはありだと思います。
今回は、共通に定義したスタイルを片方だけ変更するということが基本的にないことがわかっていたので、共通化したほうが後々楽だと判断しました。

あとがき

自分が実装したケースではこのやり方がフィットしましたが、子コンポーネント側のコードが二重管理になってしまうので、ケースごとに検討することが必要だと思います。

他に同じような悩みを抱えている人の糧になれば幸いです。

参考

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

CSSについて学んだことまとめ

CSS - MDN - Mozillaで学んだことをまとめます。

目次

  1. CSSとは
  2. CSSのコードの書き方

今回の学習のゴール

  • CSSとは何か、何ができるかを知る
  • 実際の使い方について知る

1. CSSとは

  • Cascading Style Sheetsの略
  • HTMLなどで記述された文書の体裁や見栄えを表現するために使用する
  • 要素がどのように表現されるのかを定義するもの
  • 「セレクタ」「プロパティ」「値」の3つで構成される
    • どの要素(セレクタ)の、何を(プロパティ)、どのように(値)するか宣言する

2. CSSのコードの書き方

CSSをHTMLにリンクさせる

  • HTMLファイルの<head>タグの中に以下を記載
    • rel属性は現在の文書からみたリンク先となるファイルとの関係性を指定
    • href属性にはスタイルシートのある場所を指定
html
<link rel="stylesheet" href="styles.css">

要素を指定しCSSを適用させる

css
li {
  list-style-type: none;    /* リストの先頭に表示するマーカー文字の種類を指定 */
}

/* 複数指定 */
p, li {
    color: green;
}

クラスを指定しCSSを適用させる

css
.special {
  color: red;
  font-weight: bold;
}

/* ある要素の特定のクラスにCSSを適用させる */ 
li.special {
  color: red;
  font-weight: bold;
}

ある要素の中にある特定の要素にCSSを適用させる

  • 2つの異なるセレクタのあいだにスペースを設けて設置
css
li em {
  color: red;
}

状態に基づいてCSSを適用させる(擬似クラス)

css
a:hover {    /* `<a>`タグの要素のホバーしたとき */ 
  text-decoration: none;
}

a:link {    /* まだ訪問されていない要素 */
  color: red;
}

a:visited {    /* ユーザーがすでに訪問したリンク */
  color: blue;
}

主要なプロパティ

プロパティ 説明
font-family フォントファミリ名や総称ファミリ名の優先順位リストを指定
font-style フォントが通常体 (normal)、筆記体 (italic)、斜体 (oblique) のどれでスタイル付けするかを設定
font-weight フォントの太さを指定
border 要素の境界を設定 境界線の幅、種類、色を指定できる
font-size フォントサイズを定義
text-decoration 下線、上線、取り消し線、点滅などのテキストの装飾を指定
text-transform テキストにおける英数字の大文字、小文字の表示を指定

値について

  • サイズを絶対値で指定する
単位 説明
in Inches / 1in = 2.54cm = 96px
px Inches / 1px = 96の1in
  • サイズを相対値で指定する
単位 説明
% 親要素基準で親要素サイズの~~%と指定できる
em 親要素基準で%と同じ考え方 50%は0.5em, 100%は1emとなる
rem ルート(html)要素に対する割合のサイズ

ボックスモデル

  • すべての要素はボックスと呼ばれる四角形の領域を生成

box_model.gif
ボックスモデル|CSSの基本|CSS HappyLife ZERO

  • content area : テキストや画像などが表示される領域
  • padding : 内容領域とborderの間にある余白
  • border : paddingの外側にある領域
  • margin : boxの最も外側にある余白
.content {
  margin: 10px 30px;    
  padding: 15px;
  border: 5px solid red;
}

レイアウト

  • displayプロパティ
    • ブロック要素をインライン要素に、インライン要素をブロック要素にするなど、要素の状態を変更できる
    • ブロック要素
      • 見出しや段落、表など、文書を構成する基本となる要素で、一つのブロック(塊)として認識される
      • 要素の前後に改行が入り、ブロック要素同士縦に並ぶ
      • 横幅や高さ、余白を指定できる
    • インライン要素
      • 主にブロック要素の内容として用いられる要素で、文章の一部として扱われる
      • 要素の前後に改行が入らず、インライン要素同士横に並ぶ
      • 横幅や高さ、上下の余白を指定できない
    • インラインブロック要素
      • インライン要素のように改行がつかず横並びになる
      • ブロックレベル要素のように幅や高さ、余白を指定できる
.display_example1{
    display: block;    /* ブロック要素として表示される */
}
.display_example2{
    display: inline;    /* インライン要素として表示される */
}
.display_example3{
    display: inline-block;    /* インラインブロック要素として表示される */
}

display.png
【CSS】displayの使い方を総まとめ!inlineやblockの違いは?

  • floatプロパティ
    • 指定した要素に他の要素を回り込ませる位置を指定する
.float_example1{ {
  float: left;    /* 指定した要素を左寄せにし、その後に続く要素は右側に回り込む */
}
.float_example2{ {
  float: right;    /* 指定した要素を右寄せにし、その後に続く要素は左側に回り込む */
}
.float_example3{ {
  float: none;    /* 回り込みを指定しない */
}

レスポンシブデザイン

  • 画面サイズに応じて最適なデザインを表示すること
  • cssでメディアクエリを設定する
    • ブレイクポイントと呼ばれる分岐点(ex. max-width, min-width)を指定することで、その条件ごとにファイルが読み込まれる
@media screen and (min-width:480px) { 
    /* 画面サイズが480pxからはここを読み込む スマートフォンサイズ(480px)程度の画面の大きさ */
}

@media screen and (min-width:768px) and ( max-width:1024px) {
    /* 画面サイズが768pxから1024pxまではここを読み込む タブレット縦置きサイズ(768px)程度の画面の大きさ */
}

@media screen and (min-width:1024px) {
    /* 画面サイズが1024pxからはここを読み込む タブレット横置き(1024px)程度の大きさ */
}

参照

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

EC-CUBE3 の受注管理で「帳票出力」ボタンを押しやすくするカスタマイズ

モチベーション

EC-CUBE3では帳票出力プラグインを導入することで帳票出力が可能となる。
帳票出力はよく使う機能ではあるが、ボタンが「その他」項目にまとめられていて押しにくい。
少しでも簡単に帳票出力ができるようにしたい。

スクリーンショット 2019-12-04 11.23.09.png

方針

「その他」を展開して表示させることで、そのほかのボタンも押しやすくする。
JavaScriptが効かなくなるので、HTMLの構造は崩さず、CSSで変更する。

変更

src/Eccube/Resource/template/admin/Order/index.twigstylesheet ブロックに以下のようにCSSを追加します。

src/Eccube/Resource/template/admin/Order/index.twig
{% block stylesheet %}
<link rel="stylesheet" href="{{ app.config.admin_urlpath }}/assets/css/bootstrap-datetimepicker.min.css">

{# ここから追加 #}
<style type="text/css">
#result_list_main__other_menu_toggle { display: none }
#dropmenu .dropdown-menu { display: block; position: static; border: none; padding: 0; margin: 0; float:none }
#dropmenu .dropdown-menu li { display: inline-block }
</style>
{# ここまで追加 #}

{% endblock stylesheet %}

すると「その他」が展開され、以下のような表示となります。

スクリーンショット 2019-12-04 11.21.33.png

「その他」が煩わしいと思われた方はぜひ試してみてください。

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