knowledge base

マークアップ/フロントエンドエンジニアのWEB制作における備忘録です。平日はWEB屋、休日は社会人劇団の主宰・劇作家をしています。

gulpでSassをコンパイルするとcharset指定が削除される

ファイル中にマルチバイト文字があるか確認

gulp-sassモジュールを利用してsassをコンパイルしていると、以下のようにscssファイル中に文字コードを指定していても削除されてしまうことがあります。

@charset "UTF-8";

scssファイル中にマルチバイト文字があるかどうか確認してください。

どうやらgulp-sassはマルチバイト文字が存在するファイルにのみ文字コード指定を出力するようです。

ですので、コメントに日本語を記入してしまえばこのようなことにはならないのですが、このような理由で運用上ルールを追加するのはナンセンスです。

そこで、gulpの他モジュールの力を借りてこれを解決します。

使用するのはまず、gulp-headerモジュールです。

このモジュールは対象となるファイルの先頭に任意の文字列を挿入する機能を持っています。

こちらを用いて、以下のようなタスクファイルを作成します。

var gulp  = require('gulp');
var sass = require('gulp-sass');
var header = require('gulp-header');

gulp.task('sass', function(){
  gulp.src('path/to/scss/*.scss')
    .pipe(sass())
    .pipe(header('@charset "UTF-8";\n\n'))
    .pipe(gulp.dest('dest/to/css'));
});

コンパイルしたcssの先頭に「@charset "UTF-8";」の文字列を追加していいます。

しかしこれだけでは不十分です。

なぜならこのままだと実際にコメント中に日本語が入っていたり、またCSSのプロパティにおいてマルチバイト文字が使われている場合、すでに文字コード指定が出力された上にさらにgulp-headerにより文字コード指定が重複して出力されてしまいます。

具体的には次のような記述があるケースです。

■コメント中の日本後

// これは日本語によるコメントです

■font-familyの指定

body{
  font-family: "游ゴシック体", YuGothic, sans-serif;
}

■アイコンフォント等における擬似要素での指定

.icon:before{
  content: "\e006";
}

そこで、どのような場合でも文字コード指定が重複して出力されないようにしなければなりません。

マルチバイト文字の存在を判別することは現状Node.jsではできないため、ファイル中の文字列置換を行うgulp-replaceモジュールを併用します。

var gulp  = require('gulp');
var sass = require('gulp-sass');
var header = require('gulp-header');
var replace = require('gulp-replace');

gulp.task('sass', function(){
  gulp.src('path/to/scss/*.scss')
    .pipe(sass())
    .pipe(replace(/@charset "UTF-8";/g, ''))
    .pipe(header('@charset "UTF-8";\n\n'))
    .pipe(gulp.dest('dest/to/css'));
});

コンパイル結果から一度文字コード指定を取り除いた上で、改めて文字コードを追加しています。

若干冗長ではありますが、gulpではこれが精一杯の対応となります。

SVGアニメーション はじめの一歩

前提

SVGの埋め込み方法はいくつか用意されています。

  • img要素のsrc属性による埋め込み
  • object要素のdata属性による埋め込み
  • embed要素のsrc属性による埋め込み
  • CSSプロパティ(背景画像や擬似要素)による埋め込み
  • インラインSVGによる埋め込み

基本的にSVGをアニメーションさせる際、JavascriptからDOM要素として取得し、属性値を変化させることで制御するため、 アニメーションに用いたい場合は必ずインラインSVGにて読み込んでください。

アニメーションの方法

基本的にはJavascriptにて制御する方法がセオリーであると先述してしまいましたが、実のところ方法としてはJavascript制御のほかにanimate要素を用いる方法もあります。

animate要素による制御

アニメーションさせたい要素の子要素としてanimation要素を定義します。

変化させたい属性と値を記述することでアニメーションが可能です。

IEとEdgeが未対応であること、任意のタイミングでの制御、複雑な制御には対応しにくい実制作においては不向きといって良いでしょう。

以下の例をIEとEdgeで表示してもアニメーションしないことがわかります。

See the Pen SVG Demo - 01 by Shin Imae (@shinimae) on CodePen.

このanimate要素というものは、そもそもマルティメディアを扱うXMLの拡張様式であるSMIL(Synchronized Multimedia Integration Language)という技術にて提供されているAPIなのですが、まだまだ仕様が不安定で、ブラウザ対応も不透明なようです。

実際に、Chromeにおいても2015年より非推奨扱いとなっているようです。

SMIL animate要素のリファレンスも以下にご案内しておきますので、興味のあるかたは併せて参照してみるとより理解が深まるでしょう。

developer.mozilla.org

Javascriptによる制御

ということで、基本的には先述した通りDOM要素として取得し属性値を変化させることで動かします。

そのためjQueryでも制御が可能ですし、CSSで変化させることもできます。

しかし後述いたしますがCSSによるアニメーションはブラウザ対応が充分ではなく、パスを変化させることによるモーフィングもできないため、Javascriptによる制御が王道と言えるでしょう。

以下の例は、jQueryによって円の半径を変化させるサンプルです。

See the Pen SVG Demo - 02 by Shin Imae (@shinimae) on CodePen.

この例では circle要素の属性値を変化させていますが、line要素、path要素の属性値も変化させることが可能です。

これらの要素を制御することで曲線の描画やモーフィングなどHTMLにおけるDOM要素では不可能であった表現が可能になります。

ここからは、ニーズの高いアニメーションの方法についてケースごとにまとめてゆきます。

ラインアニメーション

ペンで描いたようなアニメーションです。

See the Pen SVG Demo - 03 by Shin Imae (@shinimae) on CodePen.

SVGにはpath要素を破線として描画する機能があり、その性質を逆手に取っています。

以下の記事に詳細が解説されている通り、path要素の全長に等しい値をstroke-dashoffset/stroke-dasharrayに指定します。

SVGのアニメーションで線を引く方法まとめ(IEへの対応も)|2.IDEA

アニメーションさせるにはstroke-dasharrayを0へと変化させることで実現できます。

0へと変化させるにはいくつか方法がありますが、基本的にはjQueryのanimateメソッドか、それを拡張した各種ライブラリのメソッドを使うのが良いでしょう。

ちなみに、CSSプロパティ(transition/animation)を利用してアニメーションさせる方法もありますが、残念ながらIEが未対応です(通常のDOM要素には対応しているため、SVGの属性値に対して未対応といった方が正しいでしょう)。

このように、原理がわかってしまえば実装は非常に容易なのですが、注意点が2点あります。

一つは、d要素にて指定された座標に沿って動くため、Illustratorなどのデザインツールで作成する際に点を打った「書き順」がそのままアニメーションの方向になること。

そしてもう一つはその「書き順」がそのままアニメーションの方向になるがゆえに、「線を描く方向」などの制御にはある程度の限界があること。

どうしても細かく制御したい場合は複数のpathに細分化したものをg要素にまとめ、それぞれにラインアニメーションを走らせたり、Canvasを用いたアニメーションにシフトするなどの対応が必要になります。

パスに沿ったアニメーション

See the Pen SVG Demo - 04 by Shin Imae (@shinimae) on CodePen.

こちらはネイティブにサポートされているメソッドを用いることで容易に実装が可能です。

getPointAtLengthというメソッドは任意の長さを引数にとり、対応する座標を返すものです。

連続的に引数の値を増減しながら動かす対象の座標を設定し続けることで、パスに沿ったアニメーションが可能になります。

ですので、animateメソッドにて実装することは難しく、パラパラ漫画の要領で一定時間ごとに描画処理を呼び出すことで実装します(jQueryでも内部的にはそのように実装されています)。

もしイージングを持たせたい場合は関数としてイージングを定義し、描画のタイミングでその関数を通してアニメーションさせる値を制御します。

イージングの仕組みについて、基礎的な考え方はこちらを参照ください。

app.codegrid.net

アニメーションさせたい時間に対する現在の経過時間の割合を算出し、それをn次関数や三角関数に当てはめることで、適用する値を算出します。

以下の例ではeaseOutCubicというイージングを与えています。

See the Pen SVG Demo - 04 (with Easing) by Shin Imae (@shinimae) on CodePen.

曲線の制御(パスモーフィング)

SVGの最大の利点のひとつに、曲線の表現が可能であるという点があります。

この利点を活かし、通常のDOM要素では実現が不可能な、曲線を制御するアニメーションを行うことができます。

path要素のd属性に指定されている座標を連続的に変化させることで実装するのですが、場合よっては難易度の高い理論を求められるため、pathの制御に特化したライブラリを使用するのが良いでしょう。

パスを変化させるロジックはライブラリ側で吸収してくれるため、制作者はコア機能の開発のみに注力できます。

以下は、使い勝手の良いライブラリの一つ、Snap.svgを用いたパスモーフィングの例です。

ご覧の通りjQueryライクに記述ができるため、パスモーフィング実装の垣根がぐっと下がります。

See the Pen SVG Demo - 05 by Shin Imae (@shinimae) on CodePen.

パスモーフィングは、pathの座標を徐々に変化させることで実現するため、変化前と変化後の座標の数を同一にしておかなければいけません。 しかし、それさえ注意すればライブラリの力を借りて比較的短時間で実装ができます。

また、回転や色の変化も併用することで、よりインタラクティブな動きも実装可能です。

See the Pen SVG Demo - 06 by Shin Imae (@shinimae) on CodePen.

インフォグラフィック

昨今流行しているSVGによるインフォグラフィックも、ライブラリの使用により容易に実装できます。
いくつかライブラリが公開されていますが、その中でもD3.jsが有用かと思います。

例えば、以下はオーソドックスなパイチャートですが、こういったものもD3独自のメソッドをおさえるだけで比較的短時間で実装できます。

制作者はデータのバインディングとチャートのサイズ・位置のみ気にすればよく、面倒なロジックは全てD3.js側で吸収してくれています。

deep-blend.com

また基本的なアニメーションもD3.js側で用意してあるため、以下のようなJSON形式のデータから生成するインタラクティブなグラフも比較的短時間で実装できます。

こちらもSnap.svgと同様に、細かいロジックはD3.jsで吸収される分、制作者はコアとなる機能の開発を行うのみで済みます。

See the Pen D3.js Demo - 01 by Shin Imae (@shinimae) on CodePen.

See the Pen D3.js Demo - 02 by Shin Imae (@shinimae) on CodePen.

おわりに

ここに挙げたものはあくまでも基本的な方法を一通り紹介したにすぎません。
しかし難易度が高いと思われがちなSVGアニメーションも紐解いてしまえば、一つ一つはとてもシンプルなものであることはよく分かります。
一見すると複雑なアニメーションも、実のところはシンプルなロジックの集まりであったりするので、今回の記事がきっかけでSVGアニメーション実装の一助となりましたら幸いです。

gulpで階層構造を維持して出力する

以下のようなディレクトリ構造になるようgulpを用いてSassのコンパイルを行います。

devディレクトリがSassファイルを格納する開発用、destディレクトリがコンパイル後のCSSファイルを格納する納品用ディレクトリです。

root
 │
 ├─ dev
 │   ├─ scss
 │   │   ├─ top
 │   │   │   └─ top.scss
 │   │   └─ products   
 │   │       └─ products.scss
 │   │
 │   └─ gulpfile.js
 │
 └─ dest
     ├─ top└─ top.css
     └─ products   
         └─ products.css

Sassのコンパイルをするタスクとして、以下のようなサンプルがオーソドックスな例としてよく紹介されています。

var gulp = require('gulp');
var sass = require('gulp-sass');

gulp.task('sass', function(){
  gulp.src('./scss/**/**/*.scss')
      .pipe(sass())
      .pipe(gulp.dest('../dest'));
});

しかしこの場合、出力結果は以下のようになってしまいます。

root
 │
 ├─ dev
 │   ├─ scss
 │   │   ├─ top
 │   │   │   └─ top.scss
 │   │   └─ products   
 │   │       └─ products.scss
 │   │
 │   └─ gulpfile.js
 │
 └─ dest
     ├─ top.css
     └─ products.css

destメソッドで指定されたディレクトリ内に、並列で出力されてしまいました。 そもそもgulpでの出力は、複数のファイルを1枚のファイルにビルド・出力することを前提としており、そのためデフォルトでは指定されたディレクトリ内のファイルをすべて並列したものとして扱うよう設計されています。そのため上記のような結果になってしまったようです。

しかしgulpには柔軟なAPIがいくつか提供されており、その中に階層構造を維持したまま出力するオプションが用意されています。

github.com

第二引数として渡すオプションのうち、baseプロパティに対象とするディレクトリを指定することで、その階層構造を維持したままファイルをコピーすることが可能になります。

var gulp = require('gulp');
var sass = require('gulp-sass');

gulp.task('sass', function(){
  gulp.src('./scss/**/**/*.scss', { base: './scss' })
      .pipe(sass())
      .pipe(gulp.dest('../dest'));
});

こうすることで、階層構造を保ったまま出力することができます。

root
 │
 ├─ dev
 │   ├─ scss
 │   │   ├─ top
 │   │   │   └─ top.scss
 │   │   └─ products   
 │   │       └─ products.scss
 │   │
 │   └─ gulpfile.js
 │
 └─ dest
     ├─ top
     │   └─ top.css
     └─ products   
         └─ products.css

srcメソッドのオプションには他にもファイルを読み込まずにnullを返すreadプロパティや、巨大なファイルをストリームとして返すbufferプロパティなどがあるので、案件特性によって使ってみるのも良いでしょう。

出力した階層構造を制御する

しかし今後topやprodustsディレクトリにCSSだけでなくHTMLやJS、画像も格納することになった際、このままでは管理しにくくなってしまいます。

以下のように、出力先ディレクトリに新たにcssディレクトリを新設し、その中にファイルを格納するにはどうしたらよいでしょうか。

root
 │
 ├─ dev
 │   ├─ scss
 │   │   ├─ top
 │   │   │   └─ top.scss
 │   │   └─ products   
 │   │       └─ products.scss
 │   │
 │   └─ gulpfile.js
 │
 └─ dest
     ├─ top
     │   └─ css
     │       └─ top.scss
     └─ products   
         └─ css
             └─ products.css

このようなケースには出力先のファイル名などをリネームするgulp-renameモジュールを利用します。

www.npmjs.com

上記のUsageに記載の通り、このモジュールは非常に使い勝手がよく、ファイル名だけでなく拡張子やディレクトリ名まで変更することが可能です。 よく用いられる例としては圧縮したファイルに「min」を追加するようなケースがあります。 ちなみに以下のように、コールバック関数内にてファイル名を含むフルパスを取得できます。

rename(function (path) {
  console.log(path.dirname);  // ファイル名を除いたディレクトリ名
  console.log(path.basename); // 拡張子を除いたファイル名
  console.log(path.extname);  // 拡張子
})

このモジュールを次のようにパイプすることで、狙い通りのディレクトリに出力することが可能です。

var gulp = require('gulp');
var sass = require('gulp-sass');
var rename = require('gulp-rename');

gulp.task('sass', function(){
  gulp.src('./scss/**/**/*.scss', { base: './scss' })
      .pipe(sass())
      .pipe(rename(function (path) {
        path.dirname += '/css';
      }))
      .pipe(gulp.dest('../'));
});

こうすることで、出力先にcssディレクトリがなければ作成し、あればその中にファイルが格納されます。

重複になってしまいますが、出力先は以下のようになります。

root
 │
 ├─ dev
 │   ├─ scss
 │   │   ├─ top
 │   │   │   └─ top.scss
 │   │   └─ products   
 │   │       └─ products.scss
 │   │
 │   └─ gulpfile.js
 │
 └─ dest
     ├─ top
     │   └─ css
     │       └─ top.scss
     └─ products   
         └─ css
             └─ products.css

他にも様々なオプションやAPIが提供されているので、用途に合わせて使い分けることで、非常に柔軟な対応が可能になります。

Vagrantで文字化けする

Shift-JIS / EUC-JPの場合

Apacheにてデフォルトの文字コードUTF-8を指定していることが原因なので、httpd.confにて下記をコメントアウト

AddDefaultCharset UTF-8

UTF-8なのに文字化けする場合

ロケールが原因

先ほどは日本語専用の文字コードの場合でしたが、Apacheがデフォルトの文字コードにしているUTF-8であるにも関わらず文字化けしてしまうことがあります。

正確に申しますと文字化けというよりかは、コメント中に和文が入っているとそれ以降をブラウザが正しく解釈できないようです。

Boxにもよりますが、多くの場合デフォルトのロケールは英語(US)になっているそうで、これによりファイルの文字コードUTF-8だとしても正しく解釈できなくなってしまうそうです。

日本語のロケールがないことが原因のため、まず日本語パッケージをインストールしたのち、ロケールを追加します。

yum -y groupinstall "Japanese Support"
localedef -f UTF-8 -i ja_JP ja_JP.utf8

最後に、ロケールの設定ファイル(/etc/sysconfig/i18n)を編集して、システムのロケールを日本語に変更。

■変更前
LANG="en_US.UTF-8"
■変更後
LANG="ja_JP.UTF-8"

Vagrantでphpmyadminが403エラーになる

httpd.confのドキュメントルートディレクティブに以下を記述

AllowOverride All

VagrantでSSIを有効にする

Boxにもよるのかもしれませんが、多くの場合はデフォルトで無効になっています。

まずSSIをするためのモジュールがインストールされているか、httpd.confにて確認。

下記がコメントアウトされていたらコメントを外します。

LoadModule include_module modules/mod_include.so

確認後、httpd.confのドキュメントルートディレクティブにある下記を変更

■変更前
Options Indexes FollowSymLinks
■変更後
Options Includes Indexes FollowSymLinks

Vagrantのキャッシュが強すぎる

 Vagrantの共有フォルダ機能を利用して環境を構築している場合、ブラウザをスーパーリロードしてもキャッシュが消去されない場合があります。
httpd.confのドキュメントルートディレクティブに以下を記述します。

EnableMMAP Off
EnableSendfile Off