ふんばりフロントエンジニアのブログ

新米フロントエンジニアの備忘録です。ふんばり温泉タオル欲しい...

【GSAP】TweenMaxのcycleの使い方とは?

お久しぶりの更新です…

またもや忙殺されていました...


最近はシステム案件の仕事をすることも多いのですが、今回は久しぶりにwebページのコーディング案件に取り組むことに。


そして今回の案件は結構アニメーションが多かったんですよね。


以前の記事でTweenMaxとScrollMagicを連携させる、という内容を書いたのですが結構さわりの部分だけお話して終わってしまった感がありました...

以前の記事☟
hubarifront.hatenablog.com


今回取り組んだ案件でこんな使い方あったんか!的なことを見つけたこともあったので、TweenMaxのcycleの使い方についてまとめてみようと思います!

TweenMaxのcycleとは

cycleとは、TweenMaxをはじめとするGSAPの各種アニメーションタイプで利用することのできるプロパティの一つ。

今回の案件では以前の案件で使っていたアニメーション設定などを使いまわしていたのですが、今回は以前と違って少し凝ったアニメーションをかけてほしいという要望が...

その要望というのが、「複数の要素に対するアニメーションをそれぞれ食い気味でかけてほしい」との要望でした。

TweenMaxで複数の要素にアニメーションをかけるメソッドとして、「stagger系」のメソッドがありますよね。

例えば、staggerFromやstaggerToなどがそうです。

例を出すとこのような感じでアニメーションをかけてくれます。


See the Pen
tweenSimple
by nakajima masahiro (@nakajima333ta)
on CodePen.

複数の要素にアニメーションをかけるとき、上記のように書くことができるのですが、見て分かるように前のアニメーションが終わってから次のアニメーションがスタートしていますよね。

ただ、このアニメーションをそれぞれ食い気味のタイミングでかけてほしいという要望...


さて、どうしたものかと考えたのがまずfor文でそれぞれインデックス番号に応じてマイナスのdelayをかけるという方法。

サンプルを載せるとこのような感じですね。


See the Pen
tweenSimple
by nakajima masahiro (@nakajima333ta)
on CodePen.

しかし、なんかもっといい方法ありそうだよな〜と思っていた矢先、どうやら「cycle」というメソッドがあるみたい!

ということで、cycleを使ったものに書き換えてみると...


See the Pen
tweenCycle
by nakajima masahiro (@nakajima333ta)
on CodePen.

こんな感じになりました!

いやーかなりスッキリしたというか早くこの方法に出会いたかったですね...

さて、説明をするとcycleというのはstagger系でのみ利用することができるアニメーションプロパティです。

最大の特徴はこのcycleで使うことができるプロパティ「index」にあるでしょう。

実はcycleでは指定された要素(上記例では.box)を配列のように管理し、インデックス番号をアニメーションに利用することができます。

そして、このcycleの値としてdelayを設定する際に、インデックス番号を負の数で掛け算してやると、マイナスディレイが生まれるわけです。

関数でdelayの値を返しているのもポイントですね。

まあ考え方としてはfor文で回すのと変わらないのですが、せっかくcycleというプロパティがあるので使ったほうがお得ですよね(笑)

要素を時間差でかけるのは普通のdelayでできますが、マイナスの時間差をかけるのはcycleを使わないと実現できなそう。

割と調べるのに時間かかってしまったので、少しでも参考になれば嬉しいです^^

Android標準ブラウザをエミュレータでデバッグする(with AndroidStudio)

今回、Android 4.4.2の標準ブラウザでレイアウト崩れがあったんですよね笑

 

というこで、デバッグをしなければということになったのですが、Chromeなら

1.「devtool」のオプションである「more tools」->「remote devices」

2. chrome inspect にアクセス

で、実機でのデバッグはできますよね。

ただ、標準ブラウザの場合はデバッグすることができません…

 

そのため、Android標準ブラウザをデバッグする場合はAndroid Studioが必要になります。

 

AndroidStudioは、本来Androidアプリ用のIDEですが、利用するのはアプリのエミュレート機能のみ。


AndroidStudioで標準ブラウザのデバッグをする手順は下記の通り。

1. まず、適当にプロジェクトを作成。
2. 下記アイコンからエミュレートするデバイスを作る。

f:id:ma1129nm:20190226180306p:plain


3. create virtual device をクリック

f:id:ma1129nm:20190226180311p:plain




4. エミュレートしたいデバイス、OSバージョンを選択

f:id:ma1129nm:20190226180315p:plain




5. finishをクリックするとエミュレートするデバイスが完成し、下記の状態に。再生ボタンをクリックするとエミュレートが開始される。

 

f:id:ma1129nm:20190226180330p:plain



6. エミュレートされたら、下のようなデバイスが出てくる。

f:id:ma1129nm:20190226180338p:plain



この状態でブラウザを立ち上げ、デバッグしたいURLを入れればページが表示される。
デバッグを有効にするには、エミュレートされたデバイスの「設定」からUSBデバッグをONにする必要がある。

f:id:ma1129nm:20190226180342p:plain

USBデバッグがONになったら、ブラウザで「chrome://inspect/#devices」を開きます。


そうすると、Remote Deviceの所に「Android SDK built for x86」と出てくるので、inspectを押せばChromeデバッグと同じようにデバッグをすることができます。


ただ、Android4.4.2以前のインスペクタでは要素を選択できないので実質デバッグ不可能な様子…(出来た方いたら教えてほしい)

 

逆にそれ以降のバージョン、またChromeアプリで開いたブラウザの場合はふつうに要素の検証ができる、もしくは虫眼鏡マークを押してからエミュレーター上で要素をダブルクリックすることで検証することができます。

 

4.3以下はもうGoogleのサポートが切れている、ということで逃げられますけど4.4だとまだサポート対象なのがキツイところ…

 

標準ブラウザなど滅びればいいのに!!!!!

Firefoxでbuttonの子要素にimgタグがあるとイベントが上手くいかなかった件

 

さて、今回もさらっと。

 

今回のコーディングの一部で、butoonタグの子要素としてimgタグを入れ込んでおりました。

 

そしてこのimgタグにクリックイベントをセットしていたんですね。

 

しかし…

 

Firefoxだけイベントはセットできるものの、全く発火しない…

 

ChromeSafariはもちろんIEですら動いたというのに…

 

なんとなく調べていたら、Firefoxではbutton要素の子要素としてimgがある場合、event.targetは親要素であるbutton要素となる、という記述を見かけました。

 

なるほど、だからimgにセットしてもconsole.logさえ出なかったわけか…

 

もちろんbutton要素にイベントをセットすれば上手く行きます。

しかし、さしあたってbutton要素である必要はなかったのでbuttonをaにして逃げることにしました笑

 

あとFirefoxでは仮引数としてeventを明記しないとうまくeventを渡すことができないということもありますよね。

 

いままで当たり前のように記述していましたが、こういう理由だったのかと納得!

 

今回も短めですが、このへんで。

 

fetchが定義されていませんとかのエラーはpolyfillかCDNで対処しよう

今回はさらっとIEのエラー対処について。

jsonを扱う際にfetchを使ってデータを取得しているのですが、どうやらIEだとpromiseやfetchが使えないようなんですね。

ということで、まず簡単なCDNから。

<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/3.3.1/es6-promise.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.4/fetch.min.js"></script>

これらを読み込むと、IEでもしっかりfetchとpromiseを利用することができます。

そして、もう一つの方法がpromiseとfetchのpolyfillを読み込む、という方法。

こちらはみんな大好き「npm install」でパッケージをインストールします。

npm install fetch-polyfill  es6-promise

で、jsではこのようにimportしてあげれば大丈夫です。

import "es6-promise/auto";
import "fetch-polyfill";

promiseを変数名に定義しないといけない場合もあるようですが今回はこのような記述でなんとかなりました。

IEもうやだ。。。

AMP対応でインラインCSS書き出しをなんとか自動化した話 〜PHP編〜

今回は前回の続き、ということでPHPファイルについてお話していこうと思います。

hubarifront.hatenablog.com


前回お話したgulpfileで重要だったのは、PHPコマンドを叩くタスクでしたね。

そしてこのタスクにはコマンドライン引数として変更したファイル名を渡していました。

では、その叩かれているPHPファイルがどのような処理を行なっているのか、見ていきましょう!


前回に引き続き、ファイルの階層構造はこちらです。

f:id:ma1129nm:20190121000820p:plain

で、まずはじめに見ていただきたいのがテンプレートとなるindex_template.htmlファイルです。

<!doctype html>
<html amp lang="ja">
<head>
  <meta charset="utf-8">
  <title>サンプルテキスト</title>
 
  <script type="application/ld+json">
[
  {
    "@context": "http://schema.org",
    "@type": "BreadcrumbList",
    "itemListElement": [
      {
        "@type": "ListItem",
        "position": 1,
        "item": {
          "@id": "https://sample.jp/",
          "name": "テキスト"
        }
      }
    ]
  }
]
</script>
  <style amp-custom>
    /*inline css*/
  </style>
  <style amp-boilerplate>body {
    -webkit-animation: -amp-start 8s steps(1, end) 0s 1 normal both;
    -moz-animation: -amp-start 8s steps(1, end) 0s 1 normal both;
    -ms-animation: -amp-start 8s steps(1, end) 0s 1 normal both;
    animation: -amp-start 8s steps(1, end) 0s 1 normal both
  }

  @-webkit-keyframes -amp-start {
    from {
      visibility: hidden
    }
    to {
      visibility: visible
    }
  }

  @-moz-keyframes -amp-start {
    from {
      visibility: hidden
    }
    to {
      visibility: visible
    }
  }

  @-ms-keyframes -amp-start {
    from {
      visibility: hidden
    }
    to {
      visibility: visible
    }
  }

  @-o-keyframes -amp-start {
    from {
      visibility: hidden
    }
    to {
      visibility: visible
    }
  }

  @keyframes -amp-start {
    from {
      visibility: hidden
    }
    to {
      visibility: visible
    }
  }</style>
  <noscript>
    <style amp-boilerplate>body {
      -webkit-animation: none;
      -moz-animation: none;
      -ms-animation: none;
      animation: none
    }</style>
  </noscript>
</head>
<body>

</body>
</html>

はい、ご覧の通りかなり長ったらしいですがこれはほとんどAMPのデフォルトで記載しなければならないものばかり。

多分ここまではAMPのドキュメントを参照してもらえれば理解していただけると思います。

www.ampproject.org

さて、ここで肝心なのは次の部分。

<style amp-custom>
    /*inline css*/
  </style>

本来ならCSSを記載しなければならないstyleタグの中に「inline css」とコメントアウトが入っています。

これがトリガーになっていて、最終的にはこのコメントアウトコンパイルされたCSSに置換されるようになる、という想定です。

そしてHTMLファイルも編集するのはこのテンプレートファイルで、HTMLとCSSを混ぜ合わせた別のHTMLファイル、ここで言えばindex.htmlが出力されるということになります。


これは前回も貼りましたがイメージはこんな感じです。

f:id:ma1129nm:20190120234338p:plain


前置きが少し長くなってしまいましたが、ようやく本題のPHPファイルを見てみましょう。

<?php
//コマンドライン引数を取得 -> php replace.php _index_template.html
$cmdtemplate = $argv[1]; //_index_template.html
$cmdhtml = explode("_",$cmdtemplate)[1];//index

//templateディレクトリからファイル名を取得するための配列
$templatelist = array();

//第一引数に指定したファイルで第二引数に指定したファイルを置換
function replace($template,$html) {
    $css = file_get_contents("./css/style.css"); //scssからコンパイルされたCSS
    $replacing = file_get_contents("./templates/".$template);
    $replaced = str_replace("/*inline css*/",$css,$replacing);
    file_put_contents("./".$html.".html",$replaced);
}

foreach(glob('templates/*') as $file){
    if(is_file($file)){
        array_push($templatelist, htmlspecialchars($file));
    }
}

if( strpos($cmdtemplate,"html") !== true ){
    replace($cmdtemplate,$cmdhtml);
} else {
    foreach ($templatelist as $list){
        $t = explode("/",$list)[1];
        $h = explode("_",$t)[1];
        replace($t,$h);
    }
}

では、ポイントごとにコードの解説をしていきたいと思います。

まずは変数の宣言からいきましょう。

<?php
//コマンドライン引数を取得 -> php replace.php _index_template.html
$cmdtemplate = $argv[1]; //_index_template.html
$cmdhtml = explode("_",$cmdtemplate)[1];//index

//templateディレクトリからファイル名を取得するための配列
$templatelist = array();

ここでは、コマンドライン引数の取得をし、.htmlを除いた名前を取得しています。

コマンドライン引数は「$argv」で取れるらしいですね。

で、次に宣言している配列はindex_template.htmlなどのテンプレートファイルが入っているtemplatesディレクトリからファイル名を取得するための変数です。

合計AMPページが3ページほどあったのでこのような配列を宣言していますが1ページだけなら不要です。

では次はメインの関数であるreplaceを見てみましょう。

//第一引数に指定したファイルで第二引数に指定したファイルを置換
function replace($template,$html) {
    $css = file_get_contents("./css/style.css"); //SCSSからコンパイルされたCSS
    $replacing = file_get_contents("./templates/".$template);
    $replaced = str_replace("/*inline css*/",$css,$replacing);
    file_put_contents("./".$html.".html",$replaced);
}


このreplace関数は二つの引数があり、コメントアウトにも書いていますが、第一引数のファイルで第二引数に指定したファイルを置換するという関数です。

まず、コンパイルされたCSSを「file_get_contens」で取得し、変数に入れます。

そして対象となるテンプレートファイル(index_template.html)を取得し、$repacingに入れていますね。

で、その$replacingの中で「/*inline css*/」とコメントアウトされている部分を先ほどの$cssで置換し、$replacedに入れます。

この時点でindex_template.htmlにはCSSが入っていますが、このままだと「/*inline css*/」が消えてしまうので「file_put_contents」をするのは出力先であるindex.htmlにするわけです。

あとはこのreplace関数をどのように実行させるかの条件ですね。


if( strpos($cmdtemplate,"scss") !== true ){
    replace($cmdtemplate,$cmdhtml);
} else {
    foreach ($templatelist as $list){
        $t = explode("/",$list)[1];
        $h = explode("_",$t)[1];
        replace($t,$h);
    }
}

とりあえず、複数ページの想定なので共通のSCSSなどがあるわけです。

もちろんそのSCSSを編集した場合は全部のHTMLファイルに更新をかけたいのでtemplateファイルを格納している$templatewをforeachで回してreplace関数を全テンプレートファイルにかけています。

しかし、HTMLだけを編集した場合に全部のHTMLファイルに更新をかける必要はありませんよね。

そのため、コマンドライン引数、つまり変更したファイルにscssという文字がなければコマンドライン引数のファイルのみを置換する、という分岐をしています。

これでなんとか置換することができました。

実際にはこのような形に…

f:id:ma1129nm:20190127124731p:plain

ここまで来るのに結構時間がかかってしまいましたが、PHPのお勉強としてはかなり有意義だったような気がします。

またgulpでコマンドを打ったり、イベントを取得するということも始めてだったので今後色々使っていきたいですね!


ちょっとまとまりがない記事になってしまったかもしれませんが、逐一確認して修正していきます(汗

AMP対応でインラインCSS書き出しをなんとか自動化した話 〜gulpfile編〜

明けましておめでとうございます!

なかなか年末まで案件が終わらずブログが更新できませんでした…泣

 

さて、そして年末まで取り組んでいた案件というのが今回のテーマであるAMP対応です。

AMPとは簡単にいってしまえば、Webページを高速化するための枠組みですね。

AMP独自のタグやコンポーネントを使うことで簡単にスライダーやカルーセルを実現できるので便利ではありますが、結構制約が多いんですよね…

 

たとえば、自分でJSを書くことができなかったりするのでちょっと動作を変えたいな〜とかいうときはCSSやAMPのコンポーネントをなんとか組み合わせてやらなきゃいけないわけです。

今回で言えば、カルーセルにスライダーのページネーションを組み込む、的なことをやろうと思ったのですがなかなかうまく行かず…
 

つまりコーディングする前に綿密に実現可能かどうかの調査が必要になります。
 

さて、色々な制約があるAMPなのですがこれらの制約の中でも「一番やだなー」って思ったものがインラインCSSです。

 

実はAMPではリクエストを減らすために、CSSの外部ファイル読み込みができないんですね。

そしてこのCSSはどこに書いていいわけでもなの中に記述しないといけないと…

 
まあチクチク書いていけばなんとかなりますが、プレフィクスもつけたいし最終的には圧縮したいわけですよ…

そしてCSSをいじっただけでリロードとかもしてもらいたいわけですよ…

gulpを使っている私ですが、せっかくいつも使っているタスクが利用できないのはかなりもどかしい…
 

ということで!

いつも通りの環境でコーディングできるように試行錯誤しながらインラインCSSを自動化した話をお届けしたいと思います。

 
今回インラインCSSの書き出しに使ったものでいつもと異なるのはPHP

別に言語はなんでもいいと思いますが、とりあえず勉強中という事もあってPHPを使うことにいたしました。


ではこのPHPがどのようにして役に立つのか、流れをご紹介していきたいと思います。


まず、ざっくりした図から見ていきましょう!

f:id:ma1129nm:20190120234338p:plain


このように、元となる「index_template.html」ファイルにコンパイルされたCSSを取り込んで、最終的にHTMLファイルとして吐き出すというのが大まかな流れ。

SCSSのコンパイルに関してはいくらでも参考になる記事があると思いますのでここでは割愛します。


では、肝心のtemplateからHTMLファイルを生成するまでにどのようなファイルが必要になるのか、主役は二つのファイルです。

  • gulpfile.js

...ご存知の通り、gulpを実行するためのファイル。

...templateとHTMLファイルの中身を入れ替えるためのファイル。


では、gulpfile.jsから内容を見ていきましょう。

const gulp = require('gulp');
const autoprefixer = require('gulp-autoprefixer');
const plumber = require('gulp-plumber');
const sass = require('gulp-sass');
const sourcemaps = require('gulp-sourcemaps');
const sync = require('browser-sync').create();
const ps = require('child_process').exec; //コマンドを打てるプラグイン 重要!!
let changeFileName;
const srcDir = './src';
const distDir = './';

gulp.task('replace', function () {
    var command = `php replace.php ${changeFileName}`;
    ps(command);
});

gulp.task('serve', () => {
    sync.init({
        server: './',
        port: 3333,
        notify: false
    });
    gulp.watch(['./templates/**/*.html',`${srcDir}/css/style.css`], (event) => {
        changeFileName = event.path.split('/').pop();
    });
    gulp.watch([`${srcDir}/**/*.scss`], ['sass', sync.reload]);
    gulp.watch(['./templates/**/*.html',`${distDir}/css/style.css`], ['replace',sync.reload]);
});

gulp.task('sass', () => {
    return gulp.src(`${srcDir}/css/style.scss`)
        .pipe(plumber())
        .pipe(sass({
            outputStyle: 'compressed'
        }).on('error', sass.logError))
        .pipe(sourcemaps.init())
        .pipe(autoprefixer({
            browsers: ['last 2 versions', 'ie >= 11', 'iOS >= 10', 'Android >= 4.4'],
        }))
        .pipe(sourcemaps.write('.'))
        .pipe(gulp.dest(`${distDir}/css/`));
});

さて、このgulpfile.jsで見慣れないタスクはreplaceというタスクでしょう。

このタスクはが行なっている処理はいたって単純でPHPコマンドを動かすだけ。

ただ、「${changeFileName}」というコマンドライン引数を渡していることに注目して下さい。

この${changeFileName}という引数はserveタスクを見てもらえるとわかるように、watchで監視しているHTMLファイルかSCSSファイルに変更があった場合に、そのファイル名を代入するための変数です。

言い忘れてましたが、ファイルの階層構造はこのような感じです。

f:id:ma1129nm:20190121000820p:plain

変更のあったファイルを取るのは「event.path」なのでこのプロジェクトのルートからパスが取れてしまうわけです。

そのため、index_template.htmlに変更があった場合「templates/index_template.html」というパス名が取得できるんですね。

それをsplitで分割して完全にファイル名だけを取れるようにしている、というのが一番上のwatchの中で行なっている処理となっています。

で、この取得したパス名を変数に入れてそのままコマンドライン引数として渡しているのですが、次に気になるのは引数が渡されているreplace.phpですよね。

このまま書き続けてもいいのですが、なんとなく長くなりそうなのでPHPファイルに関してはまた次にお話ししようと思います。

 

 

 

 

 

gulpで「TypeError: args.cb is not a function」が出たらこれが原因

最近Webpackが流行っていますが、やっぱ馴染み慣れたgulpを使ってしまう私がいます…

今回はたまに出くわしていた「TypeError: args.cb is not a function」というエラーの対処法について一つ。

こちら、実際にエラーが出ていても動くものなんですがなんか怖いですし、エラー出るのがまず嫌なんですよね...

私はwatchのタスクを動かすときにこのエラーが出ていたのですが、おそらくこのページを見ている方も同じなのではないでしょうか?

そしてこのエラーの原因はエラー内容にも現れているように「args」にあります。

はい、argsというのはみなさん大好きな配列ですね。(白目)

私がエラーを出していた時のコードはこちらになります。

gulp.watch(['./**/*.html'], [sync.reload]);
gulp.watch(['./src/css/**/*.scss'], ['sass', sync.reload]);
gulp.watch(['./src/js/**/*.js'], ['js', sync.reload]);

こちら、何が問題かというと一番最初の監視対象である「html」のところ。
第一引数に関しては問題ないのですが、問題があるのは第二引数のタスクです。
上記のコードでは括弧でreloadさせるようにしていますが、括弧で囲っていいのは動かしたいタスクが二つ以上ある時のみのよう。
以下のように、タスクが一つの時は括弧で囲まないようにするとエラーは出なくなりました。

gulp.watch(['./**/*.html'], sync.reload);
gulp.watch(['./src/css/**/*.scss'], ['sass', sync.reload]);
gulp.watch(['./src/js/**/*.js'], ['js', sync.reload]);

特に大したことない情報かもしれませんがお役に立てれば幸いです^^