Quantcast
Channel: 漢ムラタ –福岡のホームページ制作会社・メディア総研株式会社 マグネッツ事業部
Viewing all 33 articles
Browse latest View live

はてなブログに投稿した画像をwgetとgrepで一括DLする方法

$
0
0

はてなブログからWordpressへ移行したい…。
そんな時Googleで「はてブ Wordpress 引越し」とかで調べれば腐るほど記事の引越し手順に関する記事が出てくると思います。

そう、記事の移行については・・・

問題は記事にアップロードした画像も移したい場合です。
実はこれが思ってたより簡単にはいかないんです。

そんなたいして画像をアップしていないのであれば記事内から手動で画像を保存して移してもいいでしょう。
しかし、記事数とアップした画像も大量にある場合はそんなことしてたらいつまでたっても終わりませんよね。

もう面倒くさいから画像ははてブに置いたまま直リンクでいいや、とかいいだしたらここで話が終わってしまうのでそういうのは無しで。

最近同じ状況になった時、最終的にLinuxコマンドの「wget」を利用する方法に落ち着いたので、その備忘録も兼ねて記事にしたいと思います。

wgetとgrepとは

wget」は指定したサイト全体や特定のディレクトリ、ファイル内に記述してあるURLなどを一括でDLすることができるコマンドです。

grep」は特定のテキストファイルの中から指定した正規表現に一致する行を出力するコマンドです。

どちらもUNIX/Linuxコマンドとなるので使用するのにはサーバーへSSHログインをし、コンソール画面から実行する必要があります。そうです、例の黒い画面です。

というわけで引越し先のサーバーがSSHログインできない場合は、一旦別のSSHが使用できるサーバーでwget&grepしたうえでそれを引越し先にFTPなどでアップする必要があります。
ちなみに今回自分が作業を行なった際もそのパターンでした。

作業の流れ

ざっくり言うと以下の流れになります。

  1. アップした画像一覧RSSのソースコードをwgetで取得。
  2. ①の中から画像のURL部分のみgrepで抽出。
  3. ②をwgetで画像をまとめてDL。

今回作業するサーバーについて

wgetが使えるサーバーならどこでもいいですが、今回は引越し先のサーバーを「ロリポップ」と仮定して作業します。
ロリポップへのSSHログインの方法は今回は省くので分からない人はすぐ検索でヒットするので自分で調べましょう。

下準備

SSHでログインしたら、まずDLする画像を保管するためのディレクトリを作成します。
名前は何でもいいですが、今回は仮に「backup」とします。

mkdir backup

作成できたらそのディレクトリに移動しておきます。

cd backup

wgetでRSSページのソースコードを取得

はてブでアップした画像は全て「はてなフォトライフ」というサイトへアップロードされており、ユーザー別のアップロード画像のリストは
http://f.hatena.ne.jp/【はてなID】/Hatena%20Blog/rss
となります。

もしくは、はてなフォトライフのページより画面上部の「RSS」のボタンからもアクセスできます。

wgetを実行

最初のwgetは「wget」の後にDLしたいページのURLを指定するだけです。色々なオプションを指定することもできますが今回は使わないので割愛します。
コンソール画面に戻りwgetコマンドを実行します。

wget http://f.hatena.ne.jp/【はてなID】/Hatena%20Blog/rss

実行するとbackupディレクトリ内に「rss」という名前のファイルが生成されていると思います。

テキストエディター等でこのファイルを開くと上記RSSページのソースコードが書かれているかと思います。

上手くいかない場合。

自分もそうでしたが、先ほどのrssファイルの中身がはてなフォトライフのトップページのソースコードになってる場合があります。

これは恐らくRSSページURLの打ち間違えやアップしている画像フォルダの公開範囲を設定している場合が考えられるんじゃないかなあと思いますが、自分の場合どちらも当てはまりませんでした。(後日再度試した際は問題なかったです)

その場合、手動でページのソースコードをコピーしてrssファイルの中を差し替えましょう。

全ページ分繰り返す

RSS1ページには最大50件までしか出力されないので、50個以上画像がある場合は、末尾に「?page=2」のようにページ数を指定して、同じようにwgetでソースコードを取得していきます。
ちなみにその場合吐き出されるファイル名は「rss?page=x」になります。

ソースコードを一つのファイルにまとめる

この後生成されたrssファイルひとつひとつに対してgrepで画像URLのみを抽出していくのですが、ひとつひとつ実行するのも面倒くさいので最初に生成されたrssファイルに全部まとめてしまいましょう。

ここは面倒ですが手動でひとつひとつrssファイルの末尾にコピペで追加していきます。(なんかもっと賢いやり方ある気もしますが…)

grepで画像URLを抽出

ソースコードをひとつにまとめられたら次はgrepコマンドで画像のURLのみ抽出していきましょう。

grepコマンドは抽出条件と検索対象ファイル名を指定するのが基本的な使い方になりますが、今回は抽出した結果を「photo.txt」というファイルに出力するようにします。(photo.txtは勝手に生成されるのでこっちで用意する必要はありません)

実際のコードは下記になります。

grep "https\:\/\/cdn-.*png\|https\:\/\/cdn-.*jpg" -o rss > photo.txt

これで検索対象の「rss」ファイルから「http://cdn-【何かしらの文字列】.jpg(もしくはpng)」の部分だけが「photo.txt」に抽出されているはずです。

もしphoto.txtの中身が空の場合は抽出条件部分になにか問題があると思うのでもう一度よく見直してみましょう。例えば「https」ではなく「http」にしてたなど。

wgetで画像をまとめてDL

最後に再度wgetで画像をまとめてDLしていきます。

wget -i photo.txt -nc --random-wait

オプション

今度のwgetには色々とオプションがついてますね。

-i」は直後の指定したファイルの中のURLをダウンロードするというものです。なので「photo.txt」ファイルの中に記述された画像のURLをダウンロードをしてくれます。

次に「-nc」ですが、これは仮に同じ名前のファイルがあったとしても上書きしないという指定になります。

最後の「–random-wait」は1つDLするたびにわずかな待機時間を持たせる指定になります。
特に大量の画像を一気に落とそうとする場合、待ち時間なしだとサーバーにかなり負荷がかかるようなので必ず指定しておきましょう。

証明書認証エラー

上記のコマンドを実行するとコンソール画面にズラズラとDL状況のログが流れると思います。しかし、よく見ると「ERROR」の文字が・・・

Error: cannot verify cdn-ak.f.st-hatena.com's certificate, issued by ...

「cdn-ak.f.st-hatena.comの証明書を確認できません。」とのことです。
画像のURLがhttpsによる何かしらの認証エラーなんでしょう。
エラー文の末尾に

To connect to cdn-ak.f.st-hatena.com insecurely, use '--no-check-certificate'.

「安全にcdn-ak.f.st-hatena.comに接続するには『–no-check-certificate』を使ってね」とあるので「–no-check-certificate」オプションを指定して再度試してみます。

wget --no-check-certificate -i photo.txt -nc --random-wait

またログが流れてきますが今回はERRORも見当たらないので大丈夫そうです。

FTPでbackupディレクトリを見るか、もしくはそのままlsコマンドでディレクトリ内のファイルを確認してみると画像がちゃんとDL出来ているのが確認できます。

以上でwgetとgrepによる画像DLは完了です。

最後に

使用するレンタルサーバーがSSHを使用できる場合に限定されますが、完全に全自動でとまではいかずとも割と少ない工数でまとめて画像がダウンロードできるので相当助かりますね。

はてなブログに限らず他の場面でも色々と使える機会がありそうなので、wget&grepは記憶の片隅にでも置いておくと良いんじゃないでしょうか。


A-Frameを使ってWebVRを実装してみよう

$
0
0

こんにちは。この間スピルバーグ監督の新作「レディ・プレイヤー1」を観に久しぶりに映画館へ行ってきたムラタです。

この映画は仮想現実(VR)のゲームを舞台にした近未来の世界を描いたものでしたが、近未来ということもあって「マトリックス」のような首にプラグぶっさすみたいな方法じゃなく、VRヘッドセットと360°動けまわれる床というシンプルなデバイスだったのがリアルでいかにも将来実現しそう感があってワクワクしましたね。

というか実際に自分の足で動けまわれるVRVR空間内の触感を再現するデバイスはもうあるようです。
こういったデバイスがもっと進化していって安価な値段で供給されるようになると映画のような世界もそう遠くはないんでしょうね。

そんなこんなで今回は何度目かのVRについてのお話です。

以前「Google VR Viewで360℃コンテンツをサイトに埋め込む」という記事を書きましたが、前回の「Google VR View」はページ内の1コンテンツとして360°写真を埋め込むといった形でしたが、今回使用する「A-Frame」というWebVR用のフレームワークでは1ページ丸々360°空間として処理するため、完全にWebVR特化型のコンテンツとなります。

Google VR Viewで360°コンテンツをサイトに埋め込む

A-Frameとは

「A-Frame」とはweb上で3Dを描画するための標準仕様「webGL」や、そのwebGLを簡単に扱えるようにするjavascriptライブラリ「three.js」を用いてweb上にVRに特化したコンテンツを作成することができるフレームワークになります。

また構築にあたりjavascriptをゴリゴリに書くのではなく、htmlタグとしてシーンやカメラ、オブジェクトなどの設定ができるため、かなりとっつきやすいです。

例えば

<a-scene>
    <a-sky src="image.jpg" rotation="0 170 0"></a-sky>
</a-scene>

こういった具合です。htmlをコーディングしているような感覚で記述できます。

対応ブラウザ・デバイス

WebVRの仕様を実装しているブラウザ(Firefox、chrome、eddge等)であれば閲覧すること自体には問題ありませんが、基本的にVRゴーグル・ヘッドセットでの閲覧が前提になります。

今現在対応している有名所のヘッドセットは以下の通りです。

詳しくはA-Frameのサイトの「VR Headsets & WebVR Browsers」のページを確認してください。日本語はないですが・・・

実際に使ってみる

aframe.jsの取得

それでは早速使ってみましょう。

まずA-Frameのサイトから必要なjsファイルをDLしてきます。

サイト右側の「GET STARTED」から概要ページにアクセスします。

 

左メニューから「Installation」ページへ飛び、「Production Version x.x.x」のボタンより「aframe.min.js」をダウンロードします。

もしくはCDNから読み込む場合は

<script src="https://aframe.io/releases/x.x.x/aframe.min.js"></script>

とします。

背景の設定

ではまず背景となる360°写真を設定してみましょう。

先ほどのaframe.jsを読み込んだらbody直下に「<a-scene>」というタグを記述します。
基本的にこのa-sceneタグの中に記述を行なっていくことになります。

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
	<script src="https://aframe.io/releases/0.7.1/aframe.js"></script>
	<style>
		*{
			padding: 0;
			margin: 0;
		}
		html,body{
			width: 100%;
			height: 100%;
			background: #fff;
		}
	</style>
</head>
<body>
	<a-scene>
	</a-scene>
</body>
</html>

これだけでシーン(3D空間)が形成されましたが、何も設定していないのでページを確認してもただ透明な空間ができただけです。

ではまず背景を設定してみましょう。
先ほどの<a-scene>の中に今度は「<a-sky>」と記述して、s-skyの属性としてcolorを指定します。

<a-scene>		
	<a-sky color="#24caff"></a-sky>
</a-scene>

すると空間全部が青色に変わったと思います。
a-skyは丸い球体の内側に色や画像をテクスチャとして張り付けるイメージです。

物がない単色の空間だと上下左右もまったく分からないので、次は物体を配置してみましょう。

物体の配置

まずは四角い箱、ボックスを配置してみましょう。
ボックスを配置するには「<a-box>」とします。
先ほどと同じくa-sceneの中に記述します。skyと同じくこちらも色や配置場所などを指定することができます。

まずボックスに色を指定してみます。

<a-scene>		
	<a-sky color="#24caff"></a-sky>
	<a-box color="#ccc"></a-box>
</a-scene>

これで灰色の箱が生成されましたが、位置情報を指定していないのでカメラ(自分の立ち位置)の真下に配置されてしまいます。
次に位置を指定してみましょう。位置は「position」で左からX座標、Y座標、Z座標を半角スペース区切りで指定します。

<a-scene>		
	<a-sky color="#24caff"></a-sky>
	<a-box position="-1 0.5 -3" color="#ccc"></a-box>
</a-scene>

これで初期位置の前方に箱が見えるようになったはずです。

boxに指定できる属性は他にも沢山ありますが、今回は幅と高さ奥行き、それに伴って見やすい表示位置に変更を加えてみましょう。

幅は「width」、高さは「height」、奥行きは「depth」で指定します。

<a-scene>		
	<a-sky color="#24caff"></a-sky>
	<a-box width="1" height="2" depth="50" position="-1 1.5 -27" color="#ccc"></a-box>
</a-scene>

するとこうなります。

めっちゃ奥にギュンって延びましたね。
これだけだと素っ気ないのでコルクっぽい質感のテクスチャを貼ってみましょう。
テクスチャの指定は「src」で直接画像を指定する方法と「<a-aseets>」というタグで要素として登録しておいて、それをIDでテクスチャとして指定する方法があるようです。

恐らくa-assetsとして管理する方法の方が後々分かりやすいのかなあと思います。
その辺はお好みで。

<a-scene>		
	<a-sky color="#24caff"></a-sky>
	<a-assets>
		<img id="texture" src="texture.jpg">
	</a-assets>
	<a-box width="1" height="2" depth="50" position="-1 1.5 -27" src="#texture" repeat="1 1" rotation="0 -2 0" color="#ccc"></a-box>
</a-scene>

a-assetsでテクスチャ画像「texture.jpg」を登録して、IDを指定します。
a-boxのsrc属性に登録したIDを指定することでその画像が張り付けられるということですね。

奥行きの面のテクスチャが延びてしまってますね・・・
指定できる属性のどれかで解消できると思いますが、自分も解決方法が分からなかったのでここでは割愛させてもらいます。

とりあえずここまでのデモを用意したので対応PCブラウザやスマートフォン等で確認してみてください。(スマートフォンで見ることをお勧めします

ちなみにGoogle Cardboardなどのスマホ用VRデバイスをお持ちの方は画面右下のアイコンを選択した状態で見るとより立体感を得られるようですよ。お試しあれ。

360°写真の配置

今度は現在青くしている背景にTheteなどのカメラで撮影した360°写真を配置してみましょう。

やり方はとても簡単で先ほどのa-skyの属性にsrcを指定して画像ファイルへのパスを通すだけです。
もしくはboxのテクスチャと同じでassetsで登録したIDを指定する方法でもOKです。
boxは邪魔なので今回は削除しておきます。

<a-scene>
	<a-assets>
		<img src="image.jpg" id="sky">
	</a-assets>		
	<a-sky src="#sky"></a-sky>
</a-scene>

360°写真をただ見せたいだけならこれだけで十分ですね、非常に簡単で手軽です。

VR空間内にテキストを配置する

次に空間内にテキストを配置してみましょう。
ただA-Frameはデフォルトではテキストを表示させるような仕組みは存在していません。(自分が見つけ切れていないだけかもしれませんが)
なので有志の方々が作成されたプラグインを使わせてもらって配置することになります。

テキスト表示プラグインは色々あるようですが、日本語(マルチバイト)に対応していないものが多いらしく、今回は「a-frame-html-shader」を使います。

A-Frame HTML Shader

その名の通り2次元であるhtmlとcssで構築されたDOM要素をA-Frame内に貼りつけることができるという代物です。なので正確にはテキストを配置するというよりhtml要素をA-Frame内に配置するということですね。

何はともあれgithubのページがあるのでそこからダウンロードしてきましょう。

解凍したら「/dist/aframe-html-shader.min.js」を読み込ませて、まず適当なhtmlを書いてみましょう。

<p>html要素をA-Frame内に配置する</p>

<a-scene>
	<a-assets>
		<img src="image.jpg" id="sky">
	</a-assets>
	<a-sky src="#sky" rotation="0 170 0"></a-sky>
</a-scene>

これだけだと単純にVR空間の表示領域の上に普通に2次元として表示されるだけです。

必要な手順として、まずhtml要素をVR空間の表示領域の裏に隠すということと、A-Frame内にhtmlを格納するための入れ物を用意してあげるという2つになります。

htmlを裏に隠す

これは単純にcssで隠してやればいいだけです。
先ほどの<p>を<div>で括って、その<div>に対して下記のようなスタイルを当てます。

<div style="width: 100%; height: 100%; position: fixed; left: 0; top: 0; z-index: -1; overflow: hidden">
	<p>html要素をA-Frame内に配置する</p>
</div>

これでdivで括ったhtml要素は表に出てこなくなりました。
※「display: none」で非表示にはしないでください。あくまで裏に隠すだけです。

A-Frame内に入れ物を用意する

次にA-Frame内に先ほど隠したhtml要素を格納するための入れ物を用意します。
<a-scene>内に<a-entity>を追加して下記のような属性を指定します。

<a-scene>
	<a-assets>
		<img src="image.jpg" id="sky">
	</a-assets>
	<a-sky src="#sky" rotation="0 170 0"></a-sky>
	<a-entity geometry="primitive: plane; width: 1" position="-1 1.5 -2" material="shader: html; target: #element" rotation="0 -2 0"></a-entity>
</a-scene>

geometryはオブジェクトの形状を定義することができます。箱状なのか球状なのか板状なのか、それらの幅や高さ奥行きなどもここで定義します。
今回は板状(plane)で幅は1としました。

positionは前項でも触れた通りオブジェクトの位置を指定します。

materialは形状の外見に関する指定ができます。色やテクスチャの指定などですね。今回は「aframe-html-shader」用に「shader: html」と「target: #element」を指定しています。
IDが「element」のhtml要素をテクスチャとして貼りつけるということですかね。
なので先ほどの<p>タグにIDとして「element」を指定しておきます。

<div style="width: 100%; height: 100%; position: fixed; left: 0; top: 0; z-index: -1; overflow: hidden">
	<p id="element">html要素をA-Frame内に配置する</p>
</div>

さて、これでどうなったでしょうか。

かすれたような何かが板に張り付いているのが分かります。
これは幅や高さの小さいhtml要素を1枚の画像として考えると、何となくイメージがつくかと思いますが、幅や高さが小さい画像を板の1面に合うように引き伸ばして貼りつけているのでこのように何がなんだか分からないような状態になっています。

じゃあどうすればいいかというと、html要素、正確には張り付けている<p>の幅や高さをもっと大きくしてあげればいいのです。
というわけで以下のようなスタイルを指定してみます。

<p id="element" style="font-size:15em;">html要素をA-Frame内に配置する</p>

通常のhtmlであればデカすぎるほどのフォントサイズですが、今回はこれくらいでちょうどいいくらいです。

今度はちゃんと見れるようになりましたね。
あとは余白や背景色・背景画像なんかをcssで色々調整してあげましょう。
この辺はオブジェクト自体のサイズによってもだいぶ調整が異なるので地道な調整作業が必要になります。

配置した要素にリンクを設定する

今度は配置したhtml要素に対してリンクを設定してみましょう。
「さっきのhtml要素に<a>を入れりゃいいだけじゃね?」とか思った方もいるかかもしれませんが、ハズレです。
そもそも先ほども書きましたが、出力されたhtml要素を画像としてオブジェクトに張り付けているだけなのでhtml側にリンクを設定したところで何の意味もありません。

それにPCやスマホでVRデバイス無しで見てる場合はオブジェクトをクリックやタップすることでリンクさせることができると安易に想像できますが、GoogleCardboardのようなデバイスを使った場合、手で画面を直接触ることができないですよね。

そういった場合のために「注視点カーソル」と呼ばれる、ユーザーの視点と連動して動くカーソルを用意する必要があります。
そのカーソルがオブジェクトに一定時間留まる(注視する)ことでリンクするという仕組みになります。

注視点カーソルを追加する

早速注視点カーソルを追加してみましょう。
注視点カーソルは「<a-cursol>」で追加できますが、これは「<a-camera>」の子要素として配置する必要があります。

<a-scene>
	<a-assets>
		<img src="image.jpg" id="sky">
	</a-assets>
	<a-camera>
		<a-cursor></a-cursor>
	</a-camera>
	<a-sky src="#sky" rotation="0 170 0"></a-sky>
	<a-entity geometry="primitive: plane; width: 2.5" position="-1 1.5 -2" material="shader: html; target: #element" rotation="0 -2 0"></a-entity>
</a-scene>

するとこうなります。

小さくて分かりづらいですが、サークル上のアイコンが追加されているのが分かります。この状態でカメラをぐりぐりと動かしてみると、このサークルは常に画面の中央にいると思います。これが注視点サークルです。

オブジェクトホバー時にアニメーションさせる

ユーザビリティを高めるためにこのカーソルがオブジェクトにホバーした際にちょっとしたアニメーションをさせるように手を加えてみましょう。

<a-scene>
	<a-assets>
		<img src="image.jpg" id="sky">
	</a-assets>
	<a-camera>
		<a-cursor color="black" scale="2 2">
			<a-animation 
						 begin="mouseenter" 
						 easing="ease-out" 
						 attribute="scale" 
						 fill="forwards" 
						 from="2 2 2" 
						 to="1 1 1" 
						 dur="300">
			</a-animation>
			<a-animation 
						 begin="mouseenter"
						 attribute="color" 
						 fill="forwards" 
						 from="black" 
						 to="red" 
						 dur="0">
			</a-animation>
			<a-animation 
						 begin="mouseleave" 
						 easing="ease-in" 
						 attribute="scale" 
						 fill="forwards" 
						 from="1 1 1" 
						 to="2 2 2" 
						 dur="300">
			</a-animation>
			<a-animation 
						 begin="mouseleave" 
						 attribute="color" 
						 fill="forwards" 
						 from="red" 
						 to="black" 
						 dur="0">
			</a-animation>
		</a-cursor>
	</a-camera>
	<a-sky src="#sky" rotation="0 170 0"></a-sky>
	<a-entity geometry="primitive: plane; width: 2.5" position="-2 1.5 -2" material="shader: html; target: #element" rotation="0 -2 0"></a-entity>
</a-scene>

アニメーションさせるにあたって、cursorに「color」と「scale」属性でサイズと色を指定しています。

アニメーションの指定はアニメーションさせたい要素の子要素に<a-animation>として登録していきます。

animationの各属性については下記の通りです。

beginはアニメーションを開始させるイベント名を指定します。

easingはアニメーションスピードの緩急を指定できます。

attributeは変化させる要素を指定します。

fillはアニメーション前後の状態を指定します。(違うかも)

fromはアニメーションの初めの値を指定します。

toはアニメーションの終わりの値を指定します。

durはアニメーションの時間を指定します。

今回はこれらを使って4つのアニメーションを指定しています。
1つ目は要素にサークルが乗った時にサークルの大きさを縮小するアニメーション。
2つ目は同じタイミングでサークルの色を黒から赤に変化させるアニメーション。
3つ目は要素からサークルが外れた時にサークルの大きさを拡大するアニメーション。
4つ目は同じタイミングで色を赤から黒に戻すアニメーション。

最後に肝心のリンクの設定をしていきます。

オブジェクトにリンク先を指定する

まずhtmlを貼りつけたオブジェクトに対して「data-link」として値にリンク先を指定します。
またクラス名「link-object」も付与しておきます。

<a-entity class="link-object" data-link="https://magnets.jp" geometry="primitive: plane; width: 2.5" position="-2 1.5 -2" material="shader: html; target: #element" rotation="0 -2 0"></a-entity>

次にjavascriptで以下のように記述します。

クリック(タップ)時の処理をjavascriptで指定する

<script>
	var linkElements = document.getElementsByClassName("link-object");
	for (var i = 0; i < linkElements.length; i++) {
		linkElements[i].addEventListener('click', function() {
			var link = this.getAttribute('data-link');
			location.href = link;
		}, false);
	}
</script>

これで「link-object」クラスをもつすべてのオブジェクトに対して、クリック(タップ)した際に「data-link」に指定したリンク先へリンクするようになります。

注視時にもクリック判定をするように指定する

前述したようにVRデバイスを使用した際に画面をタップしなくても特定のオブジェクトを一定時間注視するだけでリンクするように設定します。

これに関しては特に難しいことはありません。
<a-cursor>に対して「fuse=”true”」を指定してやるだけです。

<a-cursor fuse="true" color="black" scale="2 2">

これで注視点カーソルがオブジェクトに対して一定時間注視した場合、クリックイベントを発火するようになりますので、先ほど記述したjavascriptも有効になるという訳です。

また発火するまでの時間を「fuse-timeput」属性を指定することで変更することもできます。デフォルトでは1500です。

<a-cursor fuse="true" fuse-timeout="3000" color="black" scale="2 2">

終わりに

自分もまだまだA-Frameの触りの部分しか触れていないですが、もっと突き詰めたら色々面白いことができそうな予感がします。

それではまた別のVRの記事でお会いしましょう。

PWAでウェブアプリとしてスマホのホーム画面に追加しよう

$
0
0

こんにちはムラタです。

今回は今後ビッグウェーブが来るかもしれないし来ないかもしれない、そんな「PWA」の触りの部分だけ実装してみたお話です。

PWAって?

Progressive Web Apps(プログレッシブ ウェブ アプリ)」の略で、Googleがモバイルユーザーの体験向上を目的として策定した仕組です。

特徴としてブラウザからアクセスするwebページとApp StoreやGooglePlayからダウンロードして使うネイティブアプリ両方の利点を兼ね備えたアプリということです。

超ざっくりいうと通常のwebページをアプリっぽい感じでユーザーに提供できるよ!みたいな感じで捉えていいと思います。たぶん。違ったらごめんなさい。

PWAでできること

PWAを実装することで以下のメリットを受けることが出来ます。

  • ページの読み込み速度の上昇
  • 通信環境が良くない場所、もしくは完全なオフライン状態でもページを閲覧可能にする。
  • 特定条件下において、ページ上にホーム画面に追加を促すバナーを自動で表示(現時点ではAndroid chromeのみ)
  • アプリでよくある「プッシュ通知」を送れる。
  • アプリだと必要なリリース審査が不要
  • App StoreやGooglePlayなどから検索してインストールといった手順が不要。webページから直接ホーム画面にインストールしてもらえる。
  • ユーザーのGPS情報を利用できる。

細かく言うともっとあるんでしょうがざっくりこんな感じでしょうか。
読み込み速度上昇やプッシュ通知などは結構使えるんじゃないでしょうか

PWAに必要な環境

魅力的機能を提供してくれるPWAですが、どのブラウザでも対応しているというわけではありません。PWA実装にあたって「Service Worker」というスクリプトを動かす必要があるのですが、IEはまあ当然といえば当然ですが対応していません。

またios safariも実は最近まで対応していなかったのですが、safari11.1からPWAの一部の機能が動くようになりました。
iosユーザーのほとんどがsafariを使っていると思うのでなるべく早く全ての機能が使えるようになって欲しいところです。

ただユーザーがPWAに対応していないブラウザを使っているからといって別にサイトが見れないというようなことはなく、単純に普通のwebサイトとして表示されるだけなので心配無用です。

現在のブラウザごとの対応状況は下記を参照してください。

それとブラウザ以外だとhttpsが必須だというのも条件に入っているので注意です。

実装方法

それでは早速必要最低限での機能でPWAを実装してみましょう。
最小限の構成ならとても簡単です。

マニフェストファイルを作成する。

まず今から作成するウェブアプリの概要をまとめたファイルを作成する必要があります。このファイルにアプリ名やアプリの簡単な説明文、アイコン画像の指定やアプリ起動時の背景色などを指定することができます。

早速「manifest.json」というjsonファイルを作成して、ルートに設置します。
今回用意したmanifest.jsonの中身は下記になります。

{
  "name"              : "PWA SAMPLE",
  "short_name"        : "PWAS",
  "description"       : "PWAのテスト",
  "start_url"         : "/?utm_source=homescreen",
  "display"           : "standalone",
  "orientation"       : "portrait",
  "background_color"  : "#000000",
  "theme_color"       : "#ffffff",
  "icons": [
    {
      "src"           : "/img/manifest_icon-192.png",
      "sizes"         : "192x192",
      "type"          : "image/png"
    },
    {
      "src"           : "/img/manifest_icon-256.png",
      "sizes"         : "256x256",
      "type"          : "image/png"
    },
    {
      "src"           : "/img/manifest_icon-512.png",
      "sizes"         : "512x512",
      "type"          : "image/png"
    }
  ]
}

name

ここにはアプリ名を入れます。ウェブアプリなのでサイト名とかが一般的でしょうか。

short_name

この記述がある場合はホーム画面に追加したアプリアイコンの下に表示されます。上記アプリ名が長すぎる場合は省略名などをここに記述すればいいと思います。

description

アプリの説明を記述します。

start_url

アプリを起動した際最初に表示するページを指定します。記述がない場合はユーザーがホーム画面へ追加した時に開いていたページになるようです。
URLパラメーターを付与しておくことで、アナリティクス等でどれくらいアプリとして使われているかを計測することもできます。

display

これはアプリとして起動した際に、どのように画面を表示させるかを指定することができます。
指定できる項目は以下になります。

  • fullscreen
    時計とかバッテリー残量などの表示も無くなり、画面全てをアプリの表示領域とします。ゲームアプリなどが大体これに当てはまるのではないでしょうか。
  • standalone
    時計やバッテリー残量などの表示は残りますが、ブラウザで見る際は出ていたURLやタブボタン・メニューなどのUI部分が無くなり、アプリっぽい感じになります。
  • minimal-ui
    上記standaloneに戻るボタンなどナビゲーション制御のための最小限のUIを持たせるということらしいのですが、自分がiosで確認する限りではアプリとして認識されず普通にsafariで開かれていました・・・
  • browser
    通常のブラウザで開く処理になります。

アプリぽい感じを出したいだけならstandaloneあたりが無難じゃないかと思います。

orientation

アプリを開いた際の画面の向きを強制させることができます。
指定できる項目は以下になります。

  • landscape
    横向きになります。
  • portrait
    縦向きになります。

他にも指定できる項目があるようですが、とりあえず上記2つのどちらかを指定することになると思います。

ちなみに自分がiosで確認した所この指定は効いていませんでした。途中から追記したので反映に時間がかかるのか、そもそもiosが対応していないのか分からないですが・・・

background_color

ウェブアプリでは起動した際にほんの数秒アプリ起動画面が表示される(現時点ではandroidに限る)のですが、その際の背景色を指定することができます。

このようにアイコン画像とアプリ名と共に一瞬起動画面として表示されます。

theme_color

ステータスバーの色を指定することができます。現時点ではandroidに限ります。

実際には指定した色よりちょっと暗くした色が出るようです

icons

アプリのアイコン画像を指定します。様々なサイズのアイコンサイズを指定できますが、Googleによると最低でも192×192サイズのアイコンがあれば、それより小さいサイズに関しては用意する必要はないということらしいです。

今回は192と256と512のサイズを用意しました。

マニフェストファイルを読み込む

では作成したmanifest.jsonファイルを読み込みましょう。
head内の適当な場所にlinkで読み込みます。

<!DOCTYPE html>
<html lang="ja">

<head>
	<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=yes,maximum-scale=5">
	<meta charset="UTF-8">
	<title>PWAテスト</title>
	<link rel="stylesheet" href="style.css">
	<link rel="manifest" href="manifest.json">
</head>

問題なく読み込めているかどうかはchromeのデベロッパーツールを使うと分かりやすいです。
F12でデベロッパーツールを開いたら「Application」タブを選択します。

「APP Manifest」というタイトルでmanifest.jsonで指定した各項目が列挙されていれば問題ありません。

マニフェストファイルに関してはこれで終了です。

マニフェストファイルの変更に関して

ウェブアプリを運営していく過程で途中でマニフェストの内容を変更したい時があると思います、例えばアイコンを変更したい時や、画面の向きを固定したい時などです。

しかし、ユーザーが一度ホーム画面に追加してしまうといくらマニフェストファイルを変更してもその変更がユーザーに反映されることはありません。

変更を反映させるにはユーザーが一度ホームからウェブアプリを削除して再度ホーム画面へ追加してもらう必要があります。
そんなことをするユーザーはほとんどいないと思うので、現時点では実質一度マニフェストを決めてしまうとほぼ変更は効かないと思った方がいいです。
なのでマニフェストの内容は慎重に決めましょう。

Service Workerの登録

マニフェストの設定が終わったら次に「Service Worker」を登録します。

Service Workerとは

Service Workerというのはwebページとは別にバックグラウンド実行されるスクリプトで、これを使うことでオフラインでもページを表示できたりプッシュ通知を実装できちゃう凄い奴です。(正確にはService Worker単体での機能ではないようですが)

当然今回実装するPWAもこのService Workerが必須になります。

serviceworker.js

まずルートディレクトリに「serviceworker.js」ファイルを作成して設置します。(名前は別に任意でもOKです)
この中にService Workeの関する様々な処理を記述していくのですが、今回はオフライン対応やプッシュ通知などは使わず、ウェブアプリとしてホーム画面に登録させるという最小限の機能だけにします。

その場合のserviceworker.jsの記述はこれだけです。

self.addEventListener('fetch', function(){});

登録

そして上記serviceworker.jsを登録させます。
ServiceWorkerの制御下に置きたいページのheadの中に登録の記述を書きましょう。

<script>
if('serviceWorker' in navigator){
	navigator.serviceWorker.register('/serviceworker.js').then(function(){
		console.log("Service Worker Registered");
	});
}
</script>

まず「if(‘serviceWorker’ in navigator)」でService Worker APIが存在しているかチェックをして、存在していれば今度は「serviceworker.js」ファイルをチェックして問題なければ登録成功のログを出すような流れです。

ちなみにserviceworker.jsから下の階層のみが制御下に置かれるのでファイルの置き場所には注意してください。

PCでの確認

一旦実機で確認する前にchromeのデベロッパーツールで確認してみましょう。

マニフェストファイルの時と同じく「Application」タブを選択して、「Identity」という項目に「Add to homescreen」というテキストリンクをクリックします。

すると画面上部に「このサイトをシェルフに追加するといつでも使えるようになります」というメッセージが出ると思います。
出ない場合はコンソールログにエラーが出力されていると思うので、そのメッセージ通り修正を行なってください。

「追加」ボタンをクリックすると「ショートカットを作成しますか?」というダイアログが表示され、任意の名前でchromeアプリとして登録することができます。

デスクトップに作成されたショートカットアイコンを開くと

このようにブラウザではなくアプリとして立ち上がります。

chromeアプリから削除したいときは「chrome://apps/」にアクセスして該当のアプリを右クリックで「chromeから削除」を選択してください。

これでchromeでの確認は終了です。次は実際に実機で確認してみましょう。

実機での確認

Android Chromeでアクセスした場合、画面下に「ホーム画面に追加」というインストールの導線が出力されると思います。

このボタンをタップすることでホーム画面へウェブアプリのインストールを行なってくれますが、この機能はまだAndroid chromeだけでiosに関しては対応していません。
ちなみに一度「×」を押して明示的に拒否しちゃった場合はしばらく出てこなくなります。

ではiosの場合はどうやってホーム画面へウェブアプリを登録させるのかというと、ios safariのメニューバーから「ホーム画面に追加」を選択するしか今のところありません。

正直サイトがPWA化してるかどうかなんてユーザーからは全く分からないですし、そもそもウェブサイトを「ホーム画面に追加」なんてするユーザーなんているんでしょうか・・・ブックマークで事足りますよね。
というわけでiosからではウェブアプリとしてホーム画面に追加してもらうことは現状ほぼ不可能なんじゃないかと思ってしまいます。

とりあえずホーム画面への追加は完了したので確認してみましょう。

iOSの方のアイコン画像がマニフェストファイルで指定した画像になっていないことが分かりますよね。
そうなんです、iOSではまだマニフェストファイルのiconsの指定が効かない仕様なんです。(2018年8月現在)

ではiOSのアイコン画像はどこで指定するのか、答えはheadの中に下記の記述を書くことです。

<link rel="apple-touch-icon-precomposed" href="https://xxxx.com/img/manifest_icon-152.png">

rel属性に「apple-touch-icon-precomposed」とし、hrefにアイコン画像へのパスを記述します。
今回画像のサイズは152×152のみとしていますが、デバイスに併せて複数のサイズを指定することもできるそうです。

それでは一度ホーム画面からウェブアプリを削除し、再度インストールしてみます。

今度は大丈夫そうですね。
これでウェブアプリとしてホーム画面に追加するという処理はひとまず完了です。

終わり

今回はPWAの中でもホーム画面にウェブアプリとして追加させるという触りの部分だけをご紹介しましたが、オフライン対応やプッシュ通知などまだまだ使えそうな機能は沢山あります。

ただなにぶんまだiosなどの遅れもあり、フル活用するのは難しい段階だと思います。
appleもiOSやmacOSのsafariでPWA対応を進める方向になったらしいので近いうちにすべての機能が使えるようになると思いますが待ち遠しいですね。

それではまた機会があれば別のPWA記事でお会いしましょう。

サイトにWebプッシュを導入してユーザーにリアルタイムで告知しよう

$
0
0

以前投稿した「PWAでウェブアプリとしてスマホのホーム画面に追加しよう」で少しだけ触れたWEBプッシュ機能を今回は「OneSignal」というサービスを利用して実際にサイトに導入してみます。

そもそもWEBプッシュって?

WEBプッシュとは「WebWorker」という仕組みを使用して、ユーザーが該当のページどころかブラウザ自体を立ち上げていなくても、画面に通知を出す仕組みになります。

当然無作為に通知を送れるわけではなく、ユーザーが該当のサイトに訪れた際にWEBプッシュを送信しても良いというお許しを頂いた場合のみ送ることができ、ユーザーも任意のタイミングで送信を不許可にすることもできます。

現時点で対応しているブラウザは「Edge」「chrome」「Firefox」「Safari」が対応していて、主要なブラウザであれば問題なく使えます。
ただ使えるといってもブラウザごとに出力されるデザインやオプション機能などに差があり、そこに関してはchromeが頭一つ抜けているイメージです。

OneSignalへ登録

サインイン

OneSignalのサイトからまずは会員登録を行う必要があります。
Web Push Notifications」より「GET STARTED」ボタンを選択します。

githubやgoogle、facebookのいずれかのアカウントでログインするか新しく登録するかします。

新しいappの登録

ログインすると「All Aplications」の画面に遷移すると思いますので、まず「Add a New app」をクリックして新しくAPPを設定します。

何でもいいので分かりやすい名前を入力し、「CREATE」ボタンを選択します。

ダッシュボードへ画面遷移後「Edit app 【app名】」というダイアログボックスが表示されるので、「Web Push」を選択して「Next」ボタンを選択します。

次に「Configure Web Push」という画面に遷移するので、最初の「Choose Integration」から今回は「Custom Code」を選択します。

その下の「Site Setup」では「サイト名」と「サイトURL」「デフォルトアイコン」を設定します。

デフォルトアイコンに関しては最低192×192pxのサイズである必要があります。
アップロードするのではなく自分のサイト内の画像のURLで指定してもいいですが、その際は必ずhttpsである必要があります。今回はPWAサイトを前提としているので気にしないでもいいですが。

My Site is not fully HTTPS」はサイトがSSL化されていない場合の話なので無視して「SAVE」ボタンを選択します。

次の画面に移るので「Upload Files」より「DOWNLOAD ONESIGNAL SDK FILES」を選択してzipファイルをダウンロードします。

zipファイルを解凍すると以下の3つのファイルが入っています。

これらのファイルをサイトのルートディレクトリに設置する必要があるのですが、manifest.jsonファイルは既に存在しているので中身を既存のmanifest.jsonに移します。

"gcm_sender_id": "xxxxxxxxxxxxxxx",
  "gcm_sender_id_comment": "Do not change the GCM Sender ID"

この部分ですね。「gcm_sender_id_comment」はもしかしたら不要かもしれないです。

また、manifest以外の2つのjsファイルによってServiceworkerへ登録するのですが、既にPWA化しているので自前のServiceworkerに登録したjsファイルがあると思います。例えば前回の記事でいうところの「serviceworker.js」です。

両方を登録することはできないので、今回ダウンロードした「OneSignalSDKUpdaterWorker.js」と「OneSignalSDKWorker.js」の先頭に下記の記述を追加します。

importScripts('/serviceworker.js');

異なるファイル名にしている場合は適宜変更してください。

上記3ファイルのルートディレクトリへの設置が完了したら先ほどの画面に戻ります。

次に「Add Code to Site」よりhead内に埋め込むコードが提供されているので、全ページに埋め込みます。

<script src="https://cdn.onesignal.com/sdks/OneSignalSDK.js" async=""></script>
<script>
  var OneSignal = window.OneSignal || [];
  OneSignal.push(function() {
    OneSignal.init({
      appId: "xxxxxxxxxxxxxxxxxxxxxxx",
      autoRegister: false,
      notifyButton: {
        enable: true,
      },
    });
  });
</script>

最後の「Add your first user」は無視しても大丈夫です。「FINISH」ボタンを選択してappの登録を完了させます。

通知の許可

これで問題なければサイトの右下にベルのアイコンが出ていると思います。

このベルにカーソルを合わせると「Subscribe to notifications」という通知購読を促すメッセージが出ます。
早速アイコンをクリックしてみましょう。

閲覧している端末やブラウザによって表示される位置や文言に多少違いがあると思いますが、PC版Firefoxだと下記のようにURLバーの直下に通知を許可するかどうかのポップアップが表示されます。

通知を許可する」を選択してこのサイトからの通知を受け取るようにします。ユーザーの許可を得ずに勝手に通知を送ることはできません。

正常に登録できたらベルのアイコンが小さく半透明になっていると思います。

通知登録ユーザーの確認

OneSignalの管理画面から現在何人のユーザーが通知を登録しているかをチェックできます。

管理画面上部の「USERS」を選択します。

「Segments」という画面に遷移するので、そこの「Subscribed Users」より登録したユーザーの数を知ることができます。

先ほど登録した分が反映されて「1」になっているのが分かります。
ちなみにその上の「VIEW ALL USERS」を選択すると一覧で確認することができ、ユーザーの様々な情報も見ることができます。

通知を送る

ではいよいよ登録したユーザーに対して通知を送ってみましょう。

管理画面上部メニューより「MESSAGES」を選択してメッセージ管理画面に遷移したら、「NEW PUSH」ボタンより送信する内容を設定していきます。

送る相手

まず誰に送るかを以下の3つから選択します。

  • Send to Subscribed Users
    先ほどのユーザーリストに載っているユーザー全員に送ります。
  • Send to Particular Segment(s)
    ユーザー管理画面にて設定したセグメントで送る相手を選べるようです。
    グループ分けみたいなものでしょうか。
  • Send to Test Device(s)
    ユーザー管理画面にて「テストユーザー」として設定したユーザーの中から選んで送ります。テストでプッシュ通知したい時などに使えますね。

通知内容の入力

送信先を選んだら次にメッセージの本文を入力します。

メッセージは各言語ごとに設定することができますが、デフォルトの英語の入力は必須となっています。ただ別に日本語で入力しても問題ありません。
ただこの場合だと仮にアメリカのユーザーがWEBプッシュに登録してメッセージを受け取っても日本語のメッセージになるので、恐らく登録を解除されてしまいますよね。

そういった場合、鉛筆のアイコンをクリックすると様々な言語の一覧が表示されるので分けて設定したい言語にチェックをいれて「SELECT」をクリックします。

すると「ENGLISH」の横に選択した言語のボタンが追加されるので、言語ごとにメッセージを設定することができます。

タイトルと本文を入力すると画面右側に実際に通知された際のビジュアルを確認することができます。
ただし、反映されるのはデフォルトの「ENGLISH」の内容だけです。

また「Mac OS」「windows」「Android」の3パターンでのビジュアルをタブを選択することで切り替えて確認することができます。

オプション

送信するメッセージに対して各ブラウザごとのオプションを指定することができます。

Google Chrome

アイコン
メッセージにアイコン画像を掲載することができます。
macのchromeだとメッセージの右側、windowsのchromeだと左側に出るそうです。
「+UPLOAD」ボタンより画像をアップするか、画像の絶対パスを入力することでも設定できるようです。ただしその場合は「https」である必要があります。
ここは普通にローカルから画像をアップするのが無難だと思います。
また画像の推奨サイズは「192×192px」です。

画像
メッセージに対して大きめの画像を一緒に掲載することが出来ます。
ただしこれは現時点ではwindowsのchromeとandroidのみの設定になり、macの場合はメッセージの右側に小さい画像として表示されるようです。
アップ方法は「アイコン」と同じで、推奨サイズは「360×180px」で2:1の比率のものになります。

バッジ
Android chromeのみの設定で、通常chromeのロゴが表示されている箇所の画像を任意の画像に変えることができます。
推奨サイズは「72×72px」です。

Edge

現時点では特にオプションはないようです。今後増えることに期待しましょう。

Firefox

アイコン
前述のGoogleChromeと同様のオプションになります。
現時点でFirefoxが設定できるオプションはこれのみです。

アディショナルデータ

通知が開かれた際、ここで設定したキーと値のペア情報が受け取れるらしいです。
ちょっと活用方法が思い浮かばないです。

遷移先URL

メッセージをクリック(タップ)した際の遷移先URLを指定します。

優先度

Android端末に対するオプションのようですが、ちょっと理解できなかったです。

通知の有効期間

どれくらいの間通知を開かずに放置していたら通知を削除するかを秒で設定できます。デフォルトでは「259,200秒(3日間)」となっているようです。

スケジュール

通知を今すぐ送信するか決められた時間に送信するかの設定が可能です。
また送信するユーザー毎に最適化した時間帯に自動送信するなどもできるようなのですが詳細は不明です。

送信

ここまで設定が完了したらあとは送信するだけです。
画面下部の「CONFIRM」をクリックすることで送信が完了します。
「SAVE AS DRAFT」で送信前に下書きで保存することもできるようです。

送信すると無事モニター右下にこのように表示されました。(windows chromeでの受信)

送信が完了したら、先ほど送信したメッセージがクリックされたかなどのデータがリアルタイムで見れる画面に遷移します。

過去に送信したメッセージの情報が知りたい場合は、画面上部メニューの「MESSAGES」に再度アクセスすると、過去の送信分のメッセージが一覧で表示されているので、ここから確認することが可能です。

最後に

今回はここまでですが、次回はAPIを活用してWordpressで記事を公開したら自動でメッセージを送信する仕組みをご紹介したいと思います。

WPで記事を公開したら自動でWEBプッシュを送信する

$
0
0

前回の記事で「OneSignal」というサービスを活用して、購読ユーザーに対してWEBプッシュを送信する方法を書きましたが、毎回ブログを書くたびにOneSignalのサイトにアクセスして管理画面からWEBプッシュを送信するのは面倒くさいので、今回はブログを新規投稿した際に自動でWEBプッシュを送信するようにします。

サイトにWebプッシュを導入してユーザーにリアルタイムで告知しよう

OneSignal APIを利用する

OneSignalにもAPIが用意されていて、外部から様々な操作を行なう事が可能です。
今回もそのAPIを利用して自動で通知を送信するようにします。

新規記事公開時にアクションフックを設定

まずWEBプッシュを送信するにあたって、新規記事を公開した時に処理を行ないたいのでアクションフックを設定します。

add_action('transition_post_status','webpush_after_post', 10, 3);
function webpush_after_post($new_status, $old_status, $post){
	if($new_status == 'publish' && $post->post_type == 'post'){
		switch($old_status){
			case 'draft':
			case 'pending':
			case 'auto-draft':
			case 'future':
			case 'new':
				sendMessage($post);
				break;
		}
	}
}

add_actionで第一引数を「transition_post_status」とすることで、記事の状態が変化した際に「webpush_after_post」を実行するようにしています。

webpush_after_postの引数には変化前と変化後の記事のステータス状態が格納されているので、新しい状態が「publish(公開)」となっていて、尚且つブログ記事以外はWEBプッシュは不要なので投稿タイプが「post」であることを判断します。

そして尚且つ、変化前の状態が「draft(下書)」「pending(レビュー待ち)」「auto-draft(リビジョン)」「future(予約投稿)」「new(新規)」のいずれかの場合のみ記事情報を引数に「sendMessage」を実行させます。

OneSignalからWEBプッシュを送る

sendMessageは以下のような処理になります。

function sendMessage($post) {
	//APP IDとREST API KEY
	$app_id = 'xxxxxxxxxxxxxxxx';
	$rest_api_key = 'xxxxxxxxxxxxxxxx';
	
	//記事タイトル
	$post_title = $post->post_title;
	//記事URL
	$post_url = get_permalink($post->ID);
	//アイキャッチ
	$post_eyecatch = '';
	if(has_post_thumbnail($post->ID)){
		$post_eyecatch = wp_get_attachment_image_src(get_post_thumbnail_id($post->ID),'push_eyecatch_minimum');
		$post_eyecatch = $post_eyecatch[0];
	}
	
	//本文(英文は必須項目)
    $contents = array(
        "en" => '「'.strip_tags($post_title).'」を投稿しました',
        "ja" => '「'.strip_tags($post_title).'」を投稿しました'
    );
	//タイトル(英文は必須項目)※未入力の場合サイト名が自動で挿入?
    $headings = array(
        "en" => esc_html(get_bloginfo('name')),
        "ja" => esc_html(get_bloginfo('name'))
    );
	//最終的な内容を整形
    $fields = array(
		//自身のAPP ID
        'app_id' => $app_id,
		
		//誰に送信するか(必須)「Active Users」や「Inactive Users」などが指定できる。
        'included_segments' => array(
            'All'
        ),
		
		//リンク先URL(通知のクリック計測のため末尾にAnalythicsのUTMパラメーターを付与)
		'url' => esc_url($post_url).'?utm_source=notification&utm_medium=web_push',
		
		//本文
        'contents' => $contents,
		
		//タイトル
		'headings' => $headings,
		
		//アクションボタン。リンク先を分けることが可能。今のところchromeのみ対応
        //'web_buttons' => $hashes_array,
		
		//画像関連
		//chromeで使用する通知テキスト下のサムネイル写真
		'chrome_web_image' => $post_eyecatch,
		
		//android背景処理?
		'android_background_layout' => array(
			//タイトルフォントカラー
			'headings_color' => 'FFEC6767'
		),
    );
    
    $fields = json_encode($fields);
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "https://onesignal.com/api/v1/notifications");
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        'Content-Type: application/json; charset=utf-8',
        'Authorization: Basic '.$rest_api_key
    ));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($ch, CURLOPT_HEADER, FALSE);
    curl_setopt($ch, CURLOPT_POST, TRUE);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
    
    $response = curl_exec($ch);
    curl_close($ch);
    
    return $response;
}

まずAPIを使うにあたって「APP ID」と「REST API KEY」が必要になりますので、定義しておきます。

APP IDとREST APIKEYはOneSignal管理画面の「SETTINGS」から「Keys & IDs」にて確認できます。

次にタイトルやURL、サムネイル画像などを定義しておきます。引数に記事情報が渡されているのでそこから引っ張ってくる形になります。

次に実際に送信するメッセージの本文を格納します。英語版と日本語版のメッセージを用意する必要がありますが、特に切り分ける必要がない場合は両方日本語で設定して大丈夫です。
今回は簡素に『「【記事タイトル】」を投稿しました』にしました。

同じくメッセージタイトルを格納します。省略した場合はリンク先のサイト名が自動で入るらしいのですが、一応ちゃんと指定しておきましょう。

最後に最終的な形に内容を整形していきます。
まずAPP IDが必要になるので「app_id」に冒頭で定義した「$app_id」を指定します。

次に送信先を決めます。通知登録したユーザーをOneSignalの管理画面で一括管理でき、ユーザーごとにグループ分けなどができるので、特定のグループに対してのみWEBプッシュを送るということもできるようです。
今回は全員に送りたいので「include_segment」に配列で「All」を指定します。

次はメッセージをクリック(タップ)した際のリンク先を指定します。
前述で定義した「$post_url」だけでもいいですが、Analyticsで計測できるように末尾に「?utm_source=notification&utm_medium=web_push」というパラメーターを付与しておきます。計測する必要がなければ不要です。計測の見方に関しては今回は説明を省きます。

「content」はメッセ―ジ本文になるので前述の「$content」を指定して、「headings」はタイトルなので同じく「$headings」を指定します。

「chrome_web_image」はサムネイル画像のURLを指定します。ただし、この設定はchrome及びandroid端末でのみ有効で、それ以外のブラウザでは2018年11月現在ではサムネイルは出力されません。早く全ブラウザで統一されればいいんですけどね…

最後に(恐らく)Android端末でのみ有効な指定を行ないます。
「android_background_layout」の指定で、通知メッセージ背景の様々な指定ができるそうです。
例えば今回指定している「headings_color」ではタイトルテキストの文字色を指定することができます。いきなり背景関係ないような気もしますが。
色指定に関しては「RGB」ではなく透明度の指定も含んだ「ARGB」にて指定します。冒頭2文字部分が透明度に関する指定になるそうです。
「ARGB」で検索したら透明度ごとの記述がすぐ出てくると思います。

メッセージの送信にあたって、JSON形式にする必要があるそうなので、「$fields」を「json_encode」関数に通してJSON形式にしておきます。

あとは残りの処理でごにょごにょしてWEBプッシュ完了です。詳しいことはちょっと自分も理解できていないですが・・・
OneSignal APIページの「Example Code」の項を参照しました。

最後に

OneSignal APIはかなり奥が深く、かなり細かく設定をいじれそうなので調べていくと色々楽しそうですね。
ただいかんせん全て英語で翻訳しながらになるので時間がかかってしょうがないのが辛いところ…
あとまだブラウザやスマホのOSによって仕様がまちまちなのも惜しいところですね。

ColorMeShop WordPress PluginでWordPressとカラーミーを連携しよう

$
0
0

どうもムラタです。
今回はGMOペパボさんが公式で提供しているカラーミーショップとWordpressを連携させるプラグイン「ColorMeShop WordPress Plugin」を使ってみた話になります。

このプラグインを使うことで、カラーミーショップに登録した商品情報をもとにWordPress上に商品ページを自動生成し、商品の受注管理等はカラーミーショップのシステムを利用しながら、より自由なデザインやコンテンツマーケティングを駆使したEC構築が実現可能になるとのこと。

初期設定

まず管理画面のプラグインの新規追加より「ColorMeShop WordPress Plugin」で検索して「カラーミーショップ WordPress プラグイン」をインストールします。

管理メニューに「カラーミーショップ」という項目が追加されているので選択して「カラーミー連携設定」ページへ移動します。

クライアントID」や「クライアントシークレット」などを入力する項目が見えますが、まずは画面下部の手順にも記載のある通り、「カラーミーデベロッパーアカウント」を作成しておく必要があります。

カラーミーデベロッパーアカウントの作成

メールアドレスとパスワードを入力して登録すると、入力したメールアドレス宛に本登録完了のためのURLが記述されたメールが届くので、URLにアクセスして登録を完了させます。

登録が完了したら早速メールアドレスとパスワードでログインします。

ログインした先の画面には「アプリはまだありません」とあるだけなので、「アプリを作成する」ボタンをクリックしてアプリを作成します。

「アプリ名」と「リダイレクトURL」の入力を求められるので、アプリ名は任意の名前(今回は「ColorMeShop WordPress Plugin」とします)を、リダイレクトURLには「http://【ドメイン】/wp-admin/admin-ajax.php」とします。

カラーミーショップとの連携設定

アプリの保存が完了すると「クライアントID」と「クライアントシークレット」が発行されるので、プラグインの画面に戻り、画面上部の基本設定にそのふたつを入力して「変更を保存」ボタンをクリックします。

設定の保存が完了したら「カラーミーショップアカウントで認証する」というテキストリンクをクリックする。
リンク先で連携したいカラーミーショップのログインIDとパスワードの入力を促されるので入力して「ご利用内容確認へ」に進む。
※ここはデベロッパーアカウントではなくカラーミーショップのIDとパスです。

利用するアプリ名(先ほど登録したもの)と、そのアプリがどのような処理を行うかを確認させられるので、同意にチェックをいれて「アプリをインストール」をクリックします。

完了したら先ほどのプラグインの画面に戻り、「トークン」に入力がされていることが確認できます。
これで連携に必要な設定は完了です。

商品ページの作成&各種ページURLの生成

次に商品詳細ページを固定ページに作成します。
「固定ページ」→「新規追加」でとりあえず仮でページ名「商品詳細」、スラッグを「product」とします。

ページの追加が済んだら、一旦先ほどのプラグイン設定画面に戻り、「商品ページID」に追加した固定ページのIDを入力して保存します。

保存が完了すると、「商品詳細」「商品カテゴリー」「商品一覧」「サイトマップ」の各ページへのURLが指定した固定ページに付随する形で生成されるので、各ページへリンクを貼る場合はここを参考するといいでしょう。

使用できるショートコード

肝心の商品情報をどうやって取得するかですが、これはプラグインが用意しているいくつかの「ショートコード」を用いることで可能になります。

プラグインのバージョン「1.0.2」の時点で使用できるショートコードは以下の通りです。

colormeshop_product

商品ID(product_id)と取得したい情報(data)のパラメーターを一緒に指定することで任意の情報を出力することができます。
例えば、
「colormeshop_product product_id=”136356429” data=”name“」
と指定すると、その部分に商品ID「136356429」の商品名が出力されます。

ただ商品詳細ページは基本的にURLの末尾に「?colorme_item=商品ID」でアクセスすることになり、その場合URLパラメーターの商品IDを自動で取得するようになっているので、ブログ記事等で本文途中で特定の商品情報を掲載したい時以外ではこの指定は不要だと思います。

dataパラメーターについて

取得する内容を指定する「data」パラメーターはname以外にも下記の指定が可能です。

  • id…商品IDを取得
  • model…型番を取得
  • price…定価を取得
  • regular_price…通常販売価格を取得
  • members_price…会員価格を取得
  • unit…単位を取得
  • weight…重量を取得
  • simple_explain…簡易説明を取得
  • explain…商品詳細説明を取得

colormeshop_image

次は商品画像を取得することができるショートコードになります。
こちらも先ほどと同じく取得したい商品のIDとどのタイプの画像を取得するのか(type)の指定をすることができます。

typeの指定について

typeパラメーターで指定できるサイズは以下の通りです。

  • main…メイン画像を取得
  • thumbnail…サムネイルを取得
  • other1…その他の画像(other2、other3と続く)

colormeshop_option

商品のオプション情報を取得することができます。

オプションの組み合わせ(index)を指定して、その組み合わせの情報(data)を取得できます。

例えば「カラー」というオプションがあって、項目が「白」「黒」「青」とします。
更にもう一つ「サイズ」というオプションで項目が「S」「M」「L」とした場合、オプションの組み合わせは9つになるので、1~9の番号をindexパラメーターに指定させます。

index番号はカラーミーショップの画面で表示されてる「オプション一覧」の上から順番に1、2…となります。

なので
[colormeshop_option index=”1″ data=”title”]
とした場合は、「白 × S」という項目名が出力されることになります。

dataの指定について

取得する内容を指定する「data」パラメーターは下記の指定が可能です。

  • title…項目名を取得
  • stocks…在庫数を取得
  • models_number…型番を取得
  • option_price…販売価格
  • option_members_price…会員価格

colormeshop_cart_button

「カートに入れる」ボタンを出力します。
styleパラメーターを指定することで7種類のデザインをボタンに付けることができます。

styleの指定について

指定できるボタンスタイルは以下の7つです。

  • basic…シンプルなデフォルトなデザイン
  • cloth_blue…布地テクスチャの青いボタン
  • cloth_yellow…布地テクスチャの黄色ボタン
  • cloth_green…布地テクスチャの緑ボタン
  • washi…茶ぽい色のボタン。和紙のテクスチャというわけではない
  • check_blue…水色ボタン。チェック柄というわけではない
  • check_red…赤ボタン・こちらもチェック柄というわけではない

各デザインは以下の通りです。

「basic」以外はtype=imgaeでの画像になるので、cssの知識がある人は「basic」を指定して好きなデザインにカスタマイズするのが良さそうです。
それにしてもwashiとcheckは本当にこれで正常なのか気になる…

colormeshop_page

別途用意されているテンプレート名を「template」パラメーターに指定して、そのテンプレート内に情報を当て込んで出力します。
前述までの各種情報を1つにまとめたものというわけですね。とりあえず通常はこちらをメインに使っていくことになるんじゃないかと思います。

例えばデフォルトで用意されているテンプレート「default」だと以下のようになります。

「商品名」「商品詳細説明」「価格」「商品メイン画像」「購入ボタン」というようなテンプレートになっているのが分かります。

templateの指定について

デフォルトで用意されているのは先ほどの「default」のみになりますが、自分でカスタマイズしたテンプレートを「/wp-content/plugins/colormeshop/templates/product/」の中に保存して、ショートコードのtemplateパラメーターでその保存したファイル名(拡張子除く)を指定することでも使用することができます。

商品詳細に関しては以上です。デフォルトのままでは見た目がアレなので独自のテンプレートを用意してcssで見栄えを整える必要がありますね。

カテゴリーページ

カラーミーで登録している商品カテゴリーの一覧を出力するページになります。
URLは今回でいうと
http://xxxx/product/?colorme_page=categories
になりますが、プラグインの管理画面の「商品ページID」を指定しておかないと動作しません。

デフォルトで用意されているテンプレートでは「カテゴリー名」「カテゴリーID」「カテゴリー画像」「説明文」、属する小カテゴリーがあれば同じように名前やID、画像などが出力されるようになっています。

デフォルトのテンプレートは「/wp-content/plugins/colormeshop/templates/categories.php」になりますが、
内容をカスタマイズして独自のテンプレートを使用したい場合は、現在使用しているテーマディレクトリに「colorme-categories.php」という名前で設置しておくと、そちらを優先して読み込んでくれるようになります。

商品一覧ページ

カラーミーで登録している商品の一覧を出力するページになります。
URLは今回でいうと
http://xxxx/product/?colorme_page=items
になります。こちらもカテゴリーページと同じく事前に商品ページIDを指定しておく必要があります。

デフォルトで用意されているテンプレートでは「商品ID」「商品名」「販売価格」「定価」「会員向け価格」「原価」「PC用画像」「モバイル向け画像」「サムネイル画像」「その他画像」「簡易説明」「モバイル用説明」「PC用説明」が出力されるようになっています。

定価や原価など設定しなくても「0円」で出力されてしまうので、この辺は適宜調整する必要があります。

表示件数はデフォルトでは10件となっていて、それを超える分に関してはページングにて次のページで出力されます。

表示件数を変更したい場合はテンプレートファイルの中の下記部分を書き換えることで変更が可能です。

// 表示件数
$params = [
	'limit' => 10,
];

こちらもカテゴリーページと同じくデフォルトのテンプレートは「/wp-content/plugins/colormeshop/templates/items.php」にあり、
内容をカスタマイズして独自のテンプレートを使用したい場合は、現在使用しているテーマディレクトリに「colorme-items.php」という名前で設置しておくと、そちらを優先して読み込んでくれるようになります。

サイトマップ

登録した商品ごとのURLを記述したサイトマップのページです。
URLは
「http://xxx/【商品ページ】/sitemap.xml」
となっています。

最後に

一通りこのプラグインを触ってみて思ったのは、ある程度カラーミーから情報を引っ張ってこれるようになったものの、正直まだ全てwordpress側だけで完結させることは難しいのかなという感じです。

特にカテゴリー一覧に関しては今のところページとしてしか出力できないので、サイドカラムなどの全ページ共通部分に出力できるようにしたいですね(もしかしたらできるけど自分が調べきれていないんだけ・・・?)

このプラグインが提供されてからまだまだ日が浅いので、検索しても情報もまだ全然出てこないので手探りな感じもありますが、今後のアップデートでどんどんできることも増えていくんじゃないかと思うのでこれからの展開に期待ですね。

次世代画像フォーマット「WebP(ウェッピー)」を実際に使ってみよう

$
0
0

今回は「jpg」や「png」「gif」などのいわゆる画像フォーマットの中でも次世代フォーマットと呼ばれている「WebP(ウェッピー)」についてのお話です。

ことの発端はGoogle Speed Insightの「改善できる項目」でたびたび目にする下記の『次世代フォーマットでの画像の配信』という記述です。

『「JPEG 2000」「JPEG XR」「WebP」などの次世代フォーマットを使えばPNGやJPEGより圧縮率が高くファイルサイズを抑えられるので、結果読み込み速度も上がるよ』というGoogle先生からのお達しです。

普段から画像に圧縮をかけてからアップをしていますが、次世代フォーマットにすることでそれより更にファイルサイズを落とせる可能性があるということですね。

フォーマットごとの対応ブラウザ状況

じゃあ実際に次世代フォーマットを試すとなると、提示されている「JPEG 2000」「JPEG XR」「WebP」の3つの中から一体どれを選べばいいのでしょうか。

使用できる絶対条件として主要ブラウザがそのフォーマットに対応していないと何の意味もないので、それぞれのブラウザごとの対応状況を「Can I use」で調べてみましょう。(2019年4月現在)

JPEG 2000

JPEG後継として規格化されたフォーマットでJPEGより画質と圧縮率が向上されているとのこと。アップル推し

しかし悲しいかな対応しているブラウザがsafariだけだったので、今後も日の目を見ることはなさそうです。

JPEG XR

XRは「eXtended Range」の略だそうです。Microsoft推し

Microsoftが推しているだけあってIEとEdgeはバッチリ対応しているものの、他のブラウザからは総スカン食らってる模様。

WebP

WebPの概要に関しては後で書くとして、対応ブラウザは以下の通り。

全てのブラウザとはいかなかったものの、「Edge」「Firefox」「Chrome」などの主要ブラウザは対応していて、「Safari」や「iOS Safari」は残念ながら対応していないものの、Androidでは古いバージョンじゃない限り問題なく使うことができます。

そもそもWebPってなに

詳しくはWikiってもらえればと思いますが、Googleが開発した新しい静止画フォーマットで、Google推しということになります。

つまり「JPEG 2000」はアップルが、「JPEG XR」はMicrosoftが、「WebP」はGoogleがそれぞれ次世代フォーマットとして推しているような感じでしょうか。

ちなみにWebPは2010年には提供が始まってたらしく「次世代」というわりにはだいぶお年を召している印象ですね。

WEBにおいて大半が占めているJPEGやPNGの代わりになるべくして作られた規格で、もちろんgifやpngのように透過処理も扱えます。

端的に言えばjpgやpngよりファイルサイズ軽いけど画質はそんな変わらんし透過もいけるぜみたいな感じです。

画質とファイルサイズ比較

早速WebPを使って、実際に画質やファイルサイズがどれくらい異なるのか調べてみましょう。

今回は

  1. 圧縮処理をかけていないオリジナルのJPEG画像
  2. 圧縮処理をかけたJPEG画像
  3. WebP画像

この3つの条件で行なってみます。
ちなみに使用する画像ファイルは「2352 × 1568px」のそれなりの大きさのものになります。

画像の圧縮

画像の圧縮ですが、今回はGoogleが提供しているWEBサービス「Squoosh」を使って圧縮します。

出力形式は「Browser JPEG」で、Qualityは「0.75」の設定で圧縮をかけます。

WebP書き出し

WebPでの書き出しはPhotoshopでは標準対応していません。プラグインを入れれば書き出すこともできますが、今回は前述の「Squoosh」でもWebPで書き出せるとのことなのでそちらを使います。

出力形式を「WebP」にして、Effortが何の設定か分からないですが「4」、Qualityは「75」としました。

画質の比較

上記で書き出した画像を横に並べてJPEGで書き出したものが以下になります。

このサイズ感だと違いは全く分からないですね。
もうちょっと拡大してみましょう。

どうでしょう・・・ぱっと見はほとんど違いが無いように見えますが、WebPは歯の辺りがちょっとボヤってるような感じがしないでもないですね。
まあそもそもここまで拡大して表示することはないでしょうし自分的には許容範囲といえるレベルではないでしょうか。

ファイルサイズの比較

ファイルサイズは以下の通りです。

  • オリジナルJPEG…3,980KB
  • 圧縮JPEG…368KB
  • WebP…251KB

オリジナルのファイルサイズが馬鹿みたいにデカいですが、圧縮をかけることで91%減の「368KB」まで落とすことができ、WebPだと更にその上をいく94%減の「251KB」まで落とすことができました。推奨するだけはありましたね。

こうしてみると圧縮もかけずに使用することがいかにページを重くするかがはっきり分かりますね。

使用できないブラウザに対する切り分け

WebPの圧縮率が優れていることが分かったので次は実際にwebで使用していきたいのですが、冒頭で説明した通りWebPは全てのブラウザが対応しているわけではありません。

なので普通に

<img src="image/neko.webp" alt="ねこ">

なんて記述した場合、対応しているブラウザは問題ないですが、SafariやIEで閲覧した場合何も表示されないという事態になります。

picture要素での対応

切り分け手段としてまずpicture要素での方法があります。

<picture>
	<source type="image/webp" src="assets/img/neko.webp">
	<img src="assets/img/neko_compress.jpg" alt="ねこ">
</picture>

こうすることでWebPに対応しているブラウザはneko.webpが、非対応ブラウザはneko_compress.jpgが読み込まれます。

でも、画像のたびに毎回picture要素を記述するのは結構面倒じゃないですか?ソースコードも長くなってしまうし。

htaccessによる自動振り分け

いちいち毎回pictureなんか書いてられるかって人はこちら。
htaccessでjpgとpng画像ファイルに対して同名のWebPファイルが同じ階層にある場合、WebPをサポートしているブラウザはそちらを自動で読み込むという処理を行ないます。

記述に関しては調べれば色々の方が例を提示してくれているんですが、自分はWordpressプラグインの「EWWW Image Optimizer」がWebP設定時に吐き出すコードをそのまま流用しました。

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_ACCEPT} image/webp
RewriteCond %{REQUEST_FILENAME} (.*)\.(jpe?g|png)$
RewriteCond %{REQUEST_FILENAME}\.webp -f
RewriteCond %{QUERY_STRING} !type=original
RewriteRule (.+)\.(jpe?g|png)$ %{REQUEST_FILENAME}.webp [T=image/webp,E=accept:1,L]
</IfModule>
<IfModule mod_headers.c>
Header append Vary Accept env=REDIRECT_accept
</IfModule>
AddType image/webp .webp

サーバーによっては記述の調整が必要かもしれないですが、自分は今のところどのサーバーでも問題なく動いてくれています。

この記述によって例えば

  • /assets/img/neko.jpg
  • /assets/img/neko.jpg.webp

というように「同じ階層」で「同じ名前(元の拡張子含む」のWebPファイルを設置しておけば画像が切り替わってくれます。

※Edgeの場合

書いている時に気付いたのですが上記の場合、EdgeがWebPに対応しているにも関わらずJPEGを表示します。
というのも下記のページを見ると分かるのですが、Firefox、ChromeなどはAcceptリクエストヘッダーで「image/webp」を返してくれますが、Edgeは返してくれないようです。
なので最初の条件「RewriteCond %{HTTP_ACCEPT} image/webp」の時点で除外されてしまい、それ以降処理されず普通にJPEGが返されてしまうようですね。

自分程度の知識では解決策が見つからなかったので今回はEdgeに関しては諦めることにします。

本当に読み込まれているかの確認

自動で振り分けたといっても、ぱっと見はJPEGでもWebPでもほぼ一緒なので本当に切り替わっているのか分からないですよね。

その場合は、chromeやFirefoxの開発者ツールで「ネットワーク」から確認すれば分かります。

「Network」タブを開いて、画像関連だけ知りたいので「Img」を選択してフィルターをかけます。
すると下部に画像関連のリクエストが並ぶので、その中で該当の画像の「Type」が「webp」になっていることを確認します。

webpになっていなければうまく動いていないということになります。

GulpでWebPを書き出し

サイトを構築している際に都度WebP画像を前述の「Squoosh」を使って書き出すのも面倒くさいので、Gulpを使って画像を書き出した際に自動で同じ階層にWebPを生成するようにします。

gulp-webp

今回使用するプラグインは「gulp-webp」です。

このプラグインを使うことで簡単にwebp画像を書き出してくれて、仮に元の画像が「image.jpg」だとすると、書き出されるファイルは「image.webp」となります。

ただ、前述のhtaccessによる自動振り分けをする場合これだと困ったことになります。元の拡張子を含んだ同一の名前のWebPで置き換えるようになっているからです。

試行錯誤した結果、色々端折ってはいますが以下のようになりました。
通常の画像圧縮タスクとは別のタスクとして処理しています。

const webp = require("gulp-webp");
const tap = require("gulp-tap");
const rename = require("gulp-rename");

const srcpath = "public_html/";

gulp.task("webp", function () {
	return gulp.src(srcpath + "assets/.org_img/**/*.{jpg,jpeg,png}")
		.pipe(tap(function(file,t){
			var en = file.extname; //処理中のファイルの拡張子取得
			return gulp.src(file.path, {base: srcpath + 'assets/.org_img/'})
			.pipe(webp({//webp生成
				quality: 80
			}))
			.pipe(rename({//ファイル名の末尾に元の拡張子を付ける
				suffix: en
			}))
			.pipe(gulp.dest(srcpath + 'assets/img/'));
		}));
});

タスクも分けないでもっとスマートに出来そうな気もしますが、今はこれが精一杯ということで。

最後に

現状切り分けの処理の面倒臭さや画像ファイルが2倍になってしまい煩雑になってしまう可能性もあるWebPですが、それでもこの圧縮率の高さは魅力的です。

ただ如何せんiOSでは使うことが出来ないというのは最大のネックですね・・・。
制作側としては全てのブラウザが足並み揃えてくれると非常に助かるんですけどね。

gulpでスタイルガイドジェネレータ「Fractal」を使ってみる

$
0
0

7月から一児の父となったムラタです。

今回はgulpとスタイルガイドジェネレータの「Fractal」を使ってスタイルガイドを生成してみようと思います。

そもそもスタイルガイドって?

サイト制作にあたっての決まり事、いわばルールブックのようなものでしょうか。
サイトで使用するカラーや見出しやボタンなどの共通で使用するようなパーツデザインをスタイルガイドで定めておくことで、ページ数の多い大規模サイトを複数人でサイトを構築していくことになってもデザインに一貫性を持たせることができるし、ほぼ同じデザインなのにソースコードが全く別物なんていうことにもならないわけですね。

一人で構築・管理しているサイトであってもスタイルガイドを作っておくことで、久しぶりに更新が入った際に既にあるデザインのクラスを誤って重複して作ってしまうことも防げるし、別の人に更新を引き継ぐことになった時も同じような事態を防げますよね。

ただスタイルガイドの制約でがちがちに縛られると各自で柔軟な対処がしづらいという面も考慮しておく必要がありますね。

スタイルガイドジェネレーター

スタイルガイドを作成するにあたって別に手動でできなくもないですが、せっかくなのでジェネレーターを使って自動で書き出したいですよね。

調べた感じジェネレーターにも色々ありそうですが、今回は検索時に割と名前が見られたのと普段使用しているタスクランナーのgulpで使用できるということで「Fractal」を使用することにしました。

スタイルガイドジェネレーターではCSSファイル内にコメントを記述することでスタイルガイドを生成するものが多いらしいのですが、FractalではCSSファイルは触ることなく、別個に専用の設定ファイルを用意することでスタイルガイドを生成します。
ここは好みにもよるかと思いますが、自分はCSSに余計なコメントなどをごちゃごちゃ書きたくなかったのでこれは嬉しい仕様です。

実際に使ってみる

インストール

それでは早速Fractalをインストールしてみましょう。既にgulpはインストール済として話を進めます。

ディレクトリ構成は下記の通りです。
「src」の中に「styleguide」というディレクトリを切り、更にその中に「components」と「docs」で分けます。

ディレクトリの準備ができたら本体をダウンロードします。

npm install @frctl/fractal --save-dev

設定を記述

gulpfile.jsにFractalの設定を記述していきます。

const gulp = require("gulp");
//以下省略
...
//------------------------------------------------
//スタイルガイド(Fractal)
//------------------------------------------------
const fractal = require("@frctl/fractal").create();
//プロジェクト名
fractal.set('project.title','Fractalテスト');
//スタイルガイドの出力先
fractal.web.set('builder.dest', __dirname + '/styleguide');
//静的ファイル群のパス
fractal.web.set('static.path', __dirname + '/public_html/assets');
//ドキュメントディレクトリのパス
fractal.docs.set('path', __dirname + '/src/styleguide/docs');
//コンポーネントディレクトリのパス
fractal.components.set('path', __dirname + '/src/styleguide/components');
//ログ
const logger = fractal.cli.console;
//スタイルガイドタスク
function styleguide(){
	const builder = fractal.web.builder();
	builder.on('progress', (completed, total) => logger.update(`Exported ${completed} of ${total} items`, 'info'));
    builder.on('error', err => logger.error(err.message));
	return builder.build().then(()=>{
		logger.success('Fractal build completed!');
	});
}
//以下省略
...
exports.styleguide = styleguide;

プロジェクト名やスタイルガイドの出力先、参照するcssファイルの格納ディレクトリパスなど指定します。

スタイルガイドを生成

ドキュメントの設定

早速スタイルガイドを生成してみましょう。
最初にスタイルガイドのTOPページにもあたる「ドキュメント」のページの設定をしておきます。

前述のディレクトリ構成の「src/styleguide/docs」内に「index.md」という名前のファイルを作成して設置します。

そしてindex.mdには以下の記述を行ないます。

---
title: 概要
label: 概要
---
対応ブラウザ

* IE...IE11
* Edge...最新Ver
* Firefox...最新Ver
* Chrome...最新Ver

テキストテスト

冒頭の「—」で囲まれた部分にはドキュメントページに関する様々な情報を設定することができますが、今回は「label」と「title」を入力します。
それ以降の部分はページの本文となり「マークダウン記法」による入力が可能です。

ドキュメントページの生成

ここまで出来たら一旦gulpfile.jsで登録したスタイルガイドのタスクを実行してみましょう。

gulpfile.jsの「スタイルガイドの出力先」として設定したパスが生成されているかと思います。今回であれば「styleguide」というディレクトリが生成されて、その中に色々なファイルが一緒に格納されているのが分かります。

上記の「styleguide/index.html」にアクセスすると下記の画面になっているかと思います。

画面上部の青色のバー部分の「Fractalテスト」はgulpfile.jsで設定した「プロジェクト名」が入ります。それ以外の部分は先ほどの「index.md」で記載した内容が反映されます。

例えば画面左のメニュー部分の「概要」は「label」の内容が出力され、画面右のコンテンツ部分の「概要」は「title」が出力されます。
本文に関してはそれ以降のマークダウン記法で入力した内容が出力されます。

ここにサイト全体に関する仕様やルールなどを記載しておくといいと思います。

コンポーネントの生成

次にいよいよスタイルを登録していきましょう。
試しに以下のような横並びのスタイルを用意したのでそれを登録してみます。

scssの記述は以下の通りです。cssファイルとして既に「public_html/assets/css」にコンパイル済とします。

//横並びリスト
.row-list{
	display: flex;
	flex-wrap: wrap;
	list-style: none;
	padding: 0;
	margin: 0;
	&__item{
		box-sizing: border-box;
		border: 1px solid #ccc;
		border-radius: 10px;
		padding: 20px;
		width: calc(50% - 10px);
		margin-bottom: 20px;
		&:not(:nth-child(2n)){
			margin-right: 20px;
		}
	}
}

次に「src/styleguide/components」に「_preview.hbs」というファイルを設置し、以下の内容を記述します。

<!DOCTYPE html>
<html lang="js">
<head>
	<meta charset="UTF-8">
	<title>Preview Layout</title>
	<link rel="stylesheet" href="{{ path 'css/base.min.css'}}" media="all">
</head>
<body>
	{{{ yield }}}
</body>
</html>

このファイルはテンプレートのようなもので、これがiframeで読み込まれるようです。
head内に先ほどコンパイルしたcssファイルを読み込んでおきます。「path」はgulpfile.jsで「static.pasth」で指定した静的ファイルのパスが出力されます。

次に同じcomponentsの階層に登録したいスタイルのクラス名「row-list」のディレクトリを作成します。
そしてその中に「row-list.hbs」ファイルを設置し、以下の記述をします。

<ul class="row-list">
	<li class="row-list__item">テキスト要素</li>
	<li class="row-list__item">テキスト要素</li>
	<li class="row-list__item">テキスト要素</li>
	<li class="row-list__item">テキスト要素</li>
</ul>

これで一度スタイルガイドのタスクを実行してみましょう。
実行が完了したらドキュメントページを更新してみると左メニューに「COMPONENTS」という項目が増え、その中に「Row List」というメニューが追加されているのが確認できると思います。

クリックすると先ほど設定した「row-list.hbs」の中身が「.row-list」のスタイルが反映された状態で表示されています。

またその下には「HTML」や「View」「Context」「Info」「Notes」など補足情報を確認できるようになっています。
今回は最小限の構成で生成したので「HTML」くらいしか見れるものはないかと思いますが、設定ファイルを設置することで様々な補足を書き加えることが可能です。

NOTEの追加

右端のタブ「Notes」に情報を追加してみましょう。
row-list.hbsファイルと同じ階層に「README.md」というファイルを設置して、下記の内容を記述してみます。

「NOTES」に出力する内容を記述。

**マークダウン記法**で記述

* リスト
* リスト
* リスト
* リスト
* リスト

書いてる通りですが、ここでもマークダウン記法での記述が可能です。
この状態で再度スタイルガイドを生成しなおした後、アクセスし直しNotesタブを選択すると。

このように先ほどの文章が追加されています。

メニュー名の変更

次に画面左メニューの「Row List」のままだと第三者が見たときにパっと見何のスタイルなのか分かりづらいし(まあ本来はそういった分かりづらいクラス名を付けるべきではないですが)できれば日本語に変えたいですよね。

今回も先ほどと同じくrow-list.hbsファイルと同じ階層に「row-list.config.yml」というファイルを設置します。当然ですが「row-list」の部分はそこのクラス名に合わせます。

そしてrow-list.config.ymlの中に以下の記述をします。

title: "横並びリスト"
label: "横並びリスト"

これで再度スタイルガイドを生成すると。

無事メニュー名とページタイトル部分が設定した名前に変更されました。
このconfig.ymlは他にも様々な設定をすることができるので気になれば一度調べてみるといいかもですね。

グループ化

例えばボタンのスタイルがあり、それの色だけが異なるパターンが複数個あった場合、それをメニュー部分につらつらと並べるより「ボタン」という括りでまとめておきたいですよね。

仮に今回グループ化したいクラスを「.btn」とします。
まずは通常通り「src/styleguide/components」の中に「btn」というディレクトリを切り、「btn.hbs」を設置します。
中身は以下の通りです。

<button class="btn">デフォルトボタン</button>

次にバージョン違いのボタンとして「btn–red.hbs」を設置し、以下の記述にします。

<button class="btn btn--red">赤色ボタン</button>

そして今回も「btn.config.yml」ファイルを作成して、以下の記述にします。

title: "ボタン"
label: "ボタン"
variants: [
	{
		"name": "default",
		"label": "デフォルト"
	},
	{
		"name": "red",
		"label": "赤色"
	}
]

「variants」という項目の中にそれぞれバリエーションごとの名前とラベルを記述しています。
これでスタイルガイドを生成してみましょう。

メニューに「ボタン」という項目が追加され、その下層に「デフォルト」と「赤色」というメニューが連なっているのが分かります。

これでバリエーション違いのスタイルが散らばることなくまとめて確認できるようになりました。

最後に

自分もFractalを触り始めたばかりなので今回は必要最低限レベルの情報しかご紹介できませんでしたが、もっと様々な処理が行えるので時間があれば今後も勉強していければと思います。
まあなかなか複数人で構築をする機会がなかったり、スタイルガイド生成まで時間が取れないというのもありますが・・・


Web Share APIによるシェア機能を使ってみる

$
0
0

子どもが生まれてからすっかり早寝早起きになったムラタです。

何かしらのSNSを使って記事をシェアすることが当たり前になった昨今、ブログ記事やページにシェアボタンを配置することはとても多くなったと思います。

ただ実際配置するとなると大体「Facebook」や「Twitter」や「LINE」のシェアボタンだけになることが多く、その他のシェアしたいサービスへの選択肢を失わせているかと思います。

かといってシェアしたい可能性のある全てのサービスのボタンを配置するなんて勿論どう考えても現実的じゃないですよね。

まあFacebook、Twitter、Lineがあればほとんどのユーザーは事足りるといえばそうかもしれないですが、できるならユーザー側で任意のサービスを選ばせてあげられるといいと思いませんか?

割と最近使えるようになった「Web Share API」という仕組みを使うとユーザーの端末にインストールされているシェア対象のアプリを好きに選ぶことができるらしいので実際どういう感じなのか試してみました。

Web Share APIって?

W3Cで提案されているブラウザ標準機能で、OSネイティブのシェア機能をブラウザから呼び出すことができます。
つまりios safariでいえば画面下メニューにある「送る」で、Android Chromeならメニューの「共有」を選択した時にでる機能を呼び出せるということみたいですね。

現在Web Share APIにはLevel1Level2があり、Level1は「タイトル」「テキスト」「URL」のみの簡易なシェアでいわゆる一般的なシェア内容になっています。
一方Level2ではLevel1の内容に加えてユーザーが選択した「ファイル」も一緒に共有できるようになるらしいです。ファイル・・・?

利用可能ブラウザ

当然使えるブラウザと使えないブラウザがあります。

2019年10月時点ではPCは「safari」のみでスマホは「iOS safari」と「Android Chrome」のみが対応しています。

要するにほぼスマホのブラウザのみでPCのブラウザでは実質使えないと考えたほうが良さそうです。

なのでWeb Share APIを使用する際はスマホか否かで出力の切り分けが必要になってきますね。

使用条件

https」でなければこのWeb Share APIを使用することはできません。
この点はまあ大した問題ではないかと。

導入

それでは早速APIを使ってみましょう。
実装は非常に簡単で、ただ普通に動かすだけなら下記のような短い記述になります。

<button id="share">このページをシェアする</button>
<script>
	//クリックで動作するボタン
	var shareButton = document.getElementById('share');
	//ボタンクリックで実行
	shareButton.addEventListener('click', share);

	function share() {
		//web share apiをサポートしているか否か
		if (navigator.share) {
			navigator.share({
				title: document.querySelector('title').textContent,
				text: document.querySelector('meta[name="description"]').getAttribute('content'),
				url: location.href
			});
		}
		//サポートしていない場合の処理
		else {
			alert('このブラウザはシェア機能に対応していません。');
		}
	}
</script>

「if(navigator.share)」でWeb Share APIをサポートしているか否かで分岐させて、サポートしていない場合はアラートを出していますが、そもそもサポートしていない場合は押させないようにAPIによるボタンではなく通常のSNSのボタンを出すなどの出し分けをした方がスマートですね。

サポートしている場合は「navigator.share」で「title」「text」「url」の3つを指定します。見ての通り、titleはページのタイトルでtextは本文、urlはページのURLになります。
基本的にtitleタグやmetaタグのdescriptionなどから引っ張ってくるようにしておけば問題ないかと思います。

実際の動作

ios13のsafariで試してみるとボタンをタップするとios safari標準機能の「共有」を選択した時とほぼ同じUIが表示されるのが分かります。
TwitterやFacebookだけでなく、「メッセージ」や「メール」、「リマインダー」などにも送れます。

一番上のタブに「text」で指定した値が表示されていて、その下にシェアさせるアプリが並ぶようになっています。

メールで共有

試しに「メール」を選択してみます。

本文にtextの値とそのあとにurlの値が入っているのが分かりますが、「件名」にtitleが入っていませんね・・・

Gmailで共有

次にGmailを選択してみます。

今度はちゃんと件名が入っていますがどうやらtitleではなくtextが入っているようです。雲行きが怪しくなってきました。

最後に大御所Twitter、Facebook、LINEも試してみましょう。

Twitterで共有

うーん・・・なんかこざっぱりとしてる。
textは出力されているようですがOGP画像とかURLとか何もでないようですね。これだと本当に現在のページを共有しようとしているのか分からない気が・・・あと公開範囲とかの設定も無いようです。

これTwitterのアプリ上で処理してるのではなくてブラウザ上で処理されてるからこのしょぼさなんでしょうか?

LINEで共有

LINEに関してはTwitterと違ってLINEアプリ上で処理されるのでtitle、text、url、諸々出てますし、OGP画像も確認できます。

Facebookで共有

こちらもTwitterと同じくブラウザ上で動作しているようですが、ちゃんと諸々表示されてますし公開範囲や友達のタグ付け、OGP画像などもバッチリです。

こうなってくるといよいよTwitterだけあんな体たらくなのか謎ですね・・・

結論

今回ざっと試してみたWeb Share APIですが、結果として使えるか使えないかと言われると使えなくはないけど正直もうちょっと様子を見たい感じでしょうか。
特にTwitterのあの非常に残念な感じをどうにかして欲しいところですね。これはAPIというよりOS側の問題だと思いますが。

ただWeb Share APIは最近Level2が実装されたりと今後も継続的にアップデートされていくことが期待されるので、今後の動きによっては取り入れても良いんじゃないでしょうか。

gulp + Pug で静的ページコーディング

$
0
0

どうも、最近またポケモンGOに復帰しはじめた博多のポケおじことムラタです。
今年は今まで触ってこなかった色々な技術にも挑戦していきたいと思います。

そんなわけで今回は初めて「Pug」を使ってみたお話です。

「Pug」とは

まあGoogle先生にお伺いすればすぐ出てくるんですが、元々は「Jade」と呼ばれていたhtmlを効率的に記述するためのテンプレートエンジンで、Pugで記述した内容をhtmlとしてコンパイルしてくれます。
早い話がSassのhtml版だと考えれば分かり易いですね。

Pugの特徴・書き方

閉じタグが不要

Pugは通常のhtmlの書き方とは異なり、閉じタグを必要としませんし、なんならタグを囲む「<>」もいりません。
例えば

.section.section--red
	h2.section__title 見出しタイトル1
	p.intro: img(src="img/intro.jpg" alt="テキスト")
.section
	h2.section__title 見出しタイトル2
	p.intro テキストテキストテキストテキスト

と記述したものをコンパイルすると

<div class="section section--red">
	<h2 class="section__title">見出しタイトル1</h2>
	<p class="intro"><img src="img/intro.jpg" alt="テキスト"></p>
</div>
<div class="section">
	<h2 class="section__title">見出しタイトル2</h2>
	<p class="intro">テキストテキストテキストテキスト</p>
</div>

こんな感じでhtmlがコンパイルされます。
入れ子にしたい場合は改行してインデントするか、タグの後ろに「:」を入れて半角スペースを空けるとことでも可能です。後者の場合は1行でまとめられて出力されます。

ちなみに改行+インデントは絶対ルールなので、インデントじゃなくて半角スペース4つとかで処理しちゃうと容赦なくエラーが返ってきます。

あとsrcやaltなどの属性は「()」の中で指定して、内包するテキストは半角スペース空けて入力します。

classやidはcssと同じくタグの後ろに「#」と「.」で指定します。タグの指定が無いものはdivとして処理されます。

閉じタグは勝手に処理してくれるので閉じタグ忘れで表示が崩れちゃったという割と新人さんがやってしまううっかりも当然防げます。

書き方のルールに関してはEmmet使ったことある人なら似てるんですんなり受け入れられるんじゃないでしょうか?

ヘッダーやフッターなどの共通部分を外部ファイル化して読み込める

LPとか1ページものなら別に必要ないんですけど、複数のページがある場合非常に助かるのがこれです。
例えばヘッダーを「_header.pug」フッターを「_footer.pug」として別ファイルで管理して、読み込み元となるファイルから「include」で読み込むことが可能です。

例えば「_header.pug」の中身が

header.header
	.header__wrap
		h1.header__title タイトル

として。
それを読み込み元でincludeします。

include _header
.section.section--red
	h2.section__title 見出しタイトル1
	p.intro: img(src="img/intro.jpg" alt="テキスト")

こんな感じですね。include先の拡張子「.pug」は省略することができます。
これをコンパイルすると

<header class="header">
	<div class="header__wrap">
		<h1 class="header__title">タイトル</h1>
	</div>
</header>
<div class="section section--red">
	<h2 class="section__title">見出しタイトル1</h2>
	<p class="intro"><img src="img/intro.jpg" alt="テキスト"></p>
</div>

このようになります。

変数が使える

これもかなり大きい恩恵といえるでしょう。
例えばページタイトルを変数として格納しておいて、それを読み込むようにしておけば、複数ページ構築した後に「やっぱりtitleこっちに変更してください」と言われてもちまちま1ページごと修正する必要もなく、タイトルの変数を修正するだけで全ページの対応が完了します。

- var title = "サイトタイトル";

doctype html
html(lang="ja")
  head
    meta(charset="UTF-8")
    title #{title}

冒頭の「– var title」でtitleの変数を定義しています。
そしてそれを出力したい箇所に「#{変数名}」とすることで、これをコンパイルすると

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>サイトタイトル</title>
  </head>
</html>

こうなります。
titleの部分に指定した値が出力していますね。

テンプレートの継承ができる

head内やヘッダーやフッターなどの基本となる大枠を読み込んでおいて、要所要所でCSSやjavascriptなど各ページ毎に異なる記述をしたい場合に使用します。

例えば基本の大枠を「_layout.pug」として下記の記述をします。

block var
	- pagetitle = '';
	- description = '初期description';
doctype html
html(lang="ja")
	head
		meta(charset="UTF-8")
		meta(name="description" content=description)
		meta(name="viewport" content="width=device-width,initial-scale=1,user-scalable=yes,maximum-scale=5")
		meta(name="format-detection" content="telephone=no")
		title #{pagetitle}サイトタイトル
		block css
	body
		include _header
		main
			block content
		include _footer
		block js

所々にある「block xxx」という箇所がページ毎に継承を行うことができる要素になります。今回指定しているのは変数として格納しているページタイトルとdescription(block var)、読み込む外部cssファイルとjsファイル(block cssとblock js)、コンテンツ部(block content)になります。

これを読み込み元で以下のように記述して継承させます。

extend _layout

append var
	- pagetitle = 'ページタイトル|';
	- description = 'ページ毎のdescription';
	
append css
	link(href="assets/css/page.css" rel="stylesheet" as="style")
	
append js
	script(src="assets/js/page.min.js" as="script")
	
block content
	p 以下ページ毎の本文

冒頭の「extend _layout」で前述の「_layout.pug」の中身を読み込んでいます。
そして「append」で継承元(_layout.pug)のblock名を指定して、書き換える内容を記述します。

これをコンパイルすると

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="description" content="ページ毎のdescription">
    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=yes,maximum-scale=5">
    <meta name="format-detection" content="telephone=no">
    <title>ページタイトル|サイトタイトル</title>
    <link href="assets/css/page.css" rel="stylesheet" as="style">
  </head>
  <body>
    <header class="header">
        <div class="header__wrap">
            <h1 class="header__title">タイトル</h1>
        </div>
    </header>
    <main>
        <p>以下ページ毎の本文</p>
    </main>
    <footer class="footer">
      <p>フッターの内容</p>
    </footer>
    <script src="assets/js/page.min.js" as="script"></script>
  </body>
</html>

こうなります。
共通部分で新しく書き換えが必要な箇所が出てくればその都度「_layout.pug」でblockを指定して、読み込み元で「append」を指定しましょう。

分岐処理

例えばTOPページだけヘッダーのテキスト部分をh1にしてそれ以外はpにしたい時は以下のような形で分岐することも可能です。

まず先ほどの「_layout.pug」の変数に「top」を追加して、デフォルトの値を「false」にしておきます。

block var
	- title = '';
	- description = '初期description';
	- top = 'false';

次に「_header.pug」の分岐したい該当部分を下記のようにします。

header.header
	.header__wrap
		case top
			when "true"
				h1.header__title: a(href="#") タイトル
			default
				p.header__title: a(href="#") タイトル

case top」で変数topの中身によって処理を分岐させます。
when “xxx”」で分岐条件を記述して、「default」でどれにも当てはまらなかった際の処理を記述します。

ちなみに今回caseを使用していますが、「if」も当然使えますので、そこは適宜使い分ければいいと思います。

最後に読み込み元のappendで「top」を「true」とします。

extend include/_layout

append var
	- title = '';
	- description = 'ページ毎のdescription';
	- top = 'true';

これで出力するとh1としてコンパイルされ、それ以外のページはpとなります。

<header class="header">
  <div class="header__wrap">
    <h1 class="header__title"><a href="#">タイトル</a></h1>
  </div>
</header>

まあ出来たことは出来たんですけど、これa以下の要素を2重に書かないといけないのがすごくモヤりますね。今回入れ子がaだけだったのでまあこのままでもいいかもしれませんが、仮にすごく深い入れ子だった場合、それを2回も記述するのはナンセンスです。

こういった場合、次の処理の方が適しています。

mixin

はい、出ました「mixin」です。
sass使ったことがある人なら必ず目にすることがあると思いますが、何度も使うような記述を定義しておいて好きなところに呼び出すことができます。
また引数を指定することで同じ記述でも部分的に変更を加えることも可能です。

まず新しく「_mixin.pug」を作ってmixinの処理をまとめられるようにしておきます。(もしくはmixinごとにファイルを分ける?)

内容は下記の通りです。

mixin switch(tag='p')
	#{tag}.header__title: a(href="#") タイトル

switch(tag=’p’)」でswitchという名前でmixinを登録して、引数のtagにデフォルト値として「p」を指定しています。

次に「_layout.pug」の冒頭にて「_mixin.pug」をincludeしておきます。
これで好きな場所に登録したmixinを呼び出すことができます。

include _mixin
block var
	- title = '';
	- description = '初期description';
	- top = 'false';

そして先ほどのcaseの箇所を以下のように差し替えます。

header.header
	.header__wrap
		case top
			when "true"
				+switch('h1')
			default
				+switch()

「+mixin名()」で該当のmixinを呼び出すことができます。
分岐によって引数の値を変える訳ですね。未指定の場合はデフォルトの「p」が指定されます。
さっきよりだいぶスマートになりましたね。

繰り返し処理

for」や「each」を使って繰り返し処理を行うことが出来ます。
例えば

ul
	- for(var i = 0; i < 4; i++)
		li #{i}

とすると

<ul>
	<li>0</li>
	<li>1</li>
	<li>2</li>
	<li>3</li>
</ul>

とコンパイルされます。
eachだとこういう使い方ができます。

ul.products
	-
		var products = [
			{
				image: 'assets/img/product01.jpg',
				title: '商品1',
				price: '1000'
			},
			{
				image: 'assets/img/product02.jpg',
				title: '商品2',
				price: '2000'
			},
			{
				image: 'assets/img/product03.jpg',
				title: '商品3',
				price: '3000'
			}
		];
	each product in products
		li.products__item
			.products__thumbnail
				img(src=product.image alt=product.title)
			p.products__title !{product.title}
			dl.products__price
				dt.products__price__label 買取価格
				dd.products__price__number #{product.price}円

これをコンパイルすると

<ul class="products">
	<li class="products__item">
		<div class="products__thumbnail"><img src="assets/img/product01.jpg" alt="商品1"></div>
		<p class="products__title">商品1</p>
		<dl class="products__price">
			<dt class="products__price__label">買取価格</dt>
			<dd class="products__price__number">1000円</dd>
		</dl>
	</li>
	<li class="products__item">
		<div class="products__thumbnail"><img src="assets/img/product02.jpg" alt="商品2"></div>
		<p class="products__title">商品2</p>
		<dl class="products__price">
			<dt class="products__price__label">買取価格</dt>
			<dd class="products__price__number">2000円</dd>
		</dl>
	</li>
	<li class="products__item">
		<div class="products__thumbnail"><img src="assets/img/product03.jpg" alt="商品3"></div>
		<p class="products__title">商品3</p>
		<dl class="products__price">
			<dt class="products__price__label">買取価格</dt>
			<dd class="products__price__number">3000円</dd>
		</dl>
	</li>
</ul>

こういった具合です。
リストなどの同じコードを繰り返し記述するときにこれらを使うことで、コードに修正が入っても工数が少なく済みますね。

他にも細かい機能はありますが、特に多く使いそうなのはこんな感じだと思います。

gulpでpugを実行

次にgulpを使ってpugファイルを更新した際に自動でコンパイルを実行するようにします。

gulpfile.jsの記述

まずnpmで「gulp-pug」をインストールしておきます。

gulpfileへの記述はこんな感じになっています。

function html(){
	return gulp.src(srcpath + "assets/pug/**/!(_)*.pug")
		.pipe(plumber({
			errorHandler: notify.onError("Error: <%= error.message %>")
		}))
		.pipe(pug({
			pretty: true //整形して出力
		}))
		.pipe(gulp.dest('./public_html/lp/'));
}

ファイル名の頭に「_」がつくpugファイルは読み込み用のファイルなので更新してもコンパイルはしないようにしています。
また「lastRun」関数は意図的に使用しないようにして、更新の度に全pugファイル(読み込み専用ファイル除く)を処理するようにしています。
そうしないと読み込みファイルを更新しても読み込み元ファイルの処理ができなかったので…。

「pug」関数のオプション「pretty」はデフォルトは「false」で「true」にするとインデントを維持して人が見やすい状態でコンパイルしてくれます。
「true」だけだと半角スペース2つによるインデントになりますが、文字列を指定するとそれがインデントに使用されるようなので「’\t’」としておくとタブでインデントしてくれます。
あと公式のリファレンス見る限りどうも将来的にこのオプションは無くなる可能性があるようですね。そうなると圧縮一択になるということに…?

最後に

今年は新しい技術にチャレンジするということで初回はPugを触ってみたわけですが、やはりある程度仕様を理解していないと普通に組むより時間はかかってしまいますね。

ただ1,2回組んで慣れてしまえば構築スピードは上がるし、閉じタグ忘れも無くなり品質も保たれるし、パーツごとにファイルを分けられるので管理が楽になるなど、結構恩恵を受けられるのではないでしょうか。

最近9割くらいがWordpressによるサイト構築なので、今後Pugを使う機会はあまりないとは思いますが、なるべく使える機会があれば積極的に使っていきたいですね。(PugでWPのテーマファイルの作成できなくはないですが、正直そこまでやる必要はなさそうです)

Local Storageを使ってお気に入り登録したWordPressの記事を呼び出す

$
0
0

こんにちは村田です。

今回はLocal Storageを使って、ユーザー登録しなくてもユーザーが任意のWordpressの記事を「お気に入り」に登録して、登録した記事を一覧として出力するまでの流れをご紹介します。

Local Storageとは

HTML5から導入されたWebStorageと呼ばれる仕組みの一つで、特定のデータをユーザーが使用しているブラウザに保存することができます。

導入されたのはHTML5からだけどIEは8から使えるし、他のブラウザも軒並み使うことができます。

閲覧しているユーザー毎になにか情報を保存しておきたいけど、わざわざデータベースまで用意したくないときなどに便利な機能です。

同じような仕組みにCookieがありますが、Local Storageには以下のような違いがあります。

Cookie Local Storage
保存容量 最大4KB 最大5MB~10MB
サーバーへの送信 リクエスト毎 なし
保存期限 任意に決められる 無制限

最大保存容量はブラウザにもよりますが概ね5MBから10MBくらいまではいけるようです。

Cookieはサーバーとの通信の度にデータが送信され、通信リソースを少しではありますが使用することになりますが、Local Storageはそれがありません。

保存期限は無制限となっているので、ユーザーが自身で削除するか、こちら側で削除できる手段を用意してあげない限り半永久的にブラウザにデータが残り続ける事になります。

もし永続的ではなく、一時的な保存の方が都合が良い場合はCookieを使うか、Local Storageではなく、もうひとつのWebStorageとなる「Session Storage」を使用しましょう。
Session StorageはLocal Storageと保存容量やサーバーへの送信に関しては同じですがブラウザを閉じると自動的に保存した内容を破棄してくれます。

WordPressの記事に対して「お気に入りに登録」ボタンを追加する

では早速Wordpressの任意の記事をお気に入り登録できるようにしてみましょう。
まず記事一覧部分に登録用のボタンを用意します。

例えばループ部分をこんな感じ。
お気に入り登録ボタンに対してdata属性で記事のIDを格納しておきます。

<?php while(have_posts()): the_post(); ?>
<li>
	<button class="add" data-id="<?php echo $post->ID; ?>">お気に入り登録</button>
	<h2><?php the_title(); ?></h2>
	<?php the_excerpt(); ?>
</li>
<?php endwhile; ?>

で、見た目がとりあえずこんな感じ。

次に「お気に入り登録」ボタンに対してLocal Storageに格納するようJavascriptで処理します。

<script>
jQuery(function($){
	$('.archive').on('click','.add',function(){
		//追加する記事ID
		var pid = $(this).data('id');
		if(pid){
			//現在の登録内容を取得
			var favorite = JSON.parse(localStorage.getItem('favorite')) || [];
			//既に登録済か調査
			var index = favorite.indexOf(pid);
			//未登録ならIDを追加
			if(index == -1){
				favorite.push(pid);
				//ローカルストレージに格納しなおす
				localStorage.setItem('favorite',JSON.stringify(favorite));
			}
		}
	});
});
</script>

流れとして、data属性から記事IDを取得して存在が確認できたら、Local Storageから現在の登録内容を「localStorage.getItem」で取得します。
取得した登録内容に今から登録しようとしているIDが既にないかチェックして、未登録ならIDを「localStorage.setItem」で追加と言う流れになります。

Local Storageは「Key」と「Value」の1セットになっているので、今回はKeyを「favorite」としています。

また、Local Storageは一つのKeyに対して1つの値しか保存できませんが、その1つの値に対して複数のデータを入れたい場合、JSON形式などの1つのオブジェクトとして登録させます。

ただし、JSON形式のデータをそのまま格納しても上手くいかないようです。
なので格納前に「JSON.stringify」でJSON形式の値を1つの文字列に変換して格納する必要があります。

そして取得する時は当然文字列のままなのでそれを「JSON.parse」でJSON形式に戻してやるという訳です。

動作確認

実際に値が格納されているかどうかchromeのデベロッパーツールで確認してみましょう。

デベロッパーツールの「Application」から「Storage」に「Local Storage」と言う項目があると思います。

クリックして現在のドメイン名を選択すると格納されているKeyとその値が一覧で見えるようになるので、先ほど設定したKey「favorite」と押したボタンの記事IDがカンマ区切りで格納されているか確認します。

登録したら解除ボタンに反転させる

登録はできましたがこれでは登録するばかりでユーザーがお気に入りを外すことができませんね。

なのでお気に入りに登録したらボタンを「お気に入り解除」ボタンに反転させる動きを付けます。

Local Storageを一旦空にする

動作確認にあたって、既に登録済だと確認しづらいので一旦Local Storageの中身を空にしておきましょう。

chromeのデベロッパーツールで先ほどと同じところにアクセスします。
そして削除したいKeyを右クリックして「Delete」を選択することでKeyごとまるっと削除されます。

空になったところで、先ほどの登録処理の最後に登録ボタンの「add」クラスを削除して「remove」クラスを付与します。
ついでにテキストも「お気にいり解除」としておきましょう。

省略
//ローカルストレージに格納しなおす
localStorage.setItem('favorite',JSON.stringify(favorite));
//反転させる
$(this).removeClass('add').addClass('remove').text('お気にいり解除');

今度は「remove」クラスのボタンをクリックした時の登録解除の処理を記述していきます。

$('.archive').on('click','.remove',function(){
		//除外する記事ID
		var pid = $(this).data('id');
		if(pid){
			//現在の登録内容を取得
			var favorite = JSON.parse(localStorage.getItem('favorite')) || [];
			//既に登録済か調査
			var index = favorite.indexOf(pid);
			//登録済ならIDを除外
			if(index != -1){
				favorite.splice(index,1);
				//ローカルストレージに格納しなおす
				localStorage.setItem('favorite',JSON.stringify(favorite));
				//反転させる
				$(this).removeClass('remove').addClass('add').text('お気にいり登録');
			}
		}
	});

ほぼ登録の時と処理が一緒ですね。
既に登録済であればspliceメソッドで該当するインデックス番号の値を削除して、再度格納し直すという流れです。

これで登録と解除を繰り返すことができるようになりました。デベロッパーツールで値の増減を確認してみましょう。

読み込み時にボタンの状態を反映させる

今のままではページ読み込み時には全てボタンの文言が「お気に入り登録」になってしまうので、今度はページ読み込み時に登録状態を反映しましょう。

//現在の登録内容を取得
	var favorite = JSON.parse(localStorage.getItem('favorite')) || [];
	//登録内容数分繰り返し処理
	for(var i = 0; i < favorite.length; i++){
		//現在のID
		var id = favorite[i];
		//buttonのdata-id属性と上記IDが一致するものはaddクラスを外してremoveクラスを付与。テキストを変更する。
		$('button[data-id=' + id + ']').removeClass('add').addClass('remove').text('お気に入り解除');
	}

現在の登録内容を取得して、それをforで格納されている数の分だけ処理を行います。
そして格納されているIDと合致するdata-id属性を持っているbuttonに対してクラスと文言の差し替えを実行するだけです。

登録一覧ページを作成する

それではいよいよお気に入りに登録した記事を一覧表示するページを作っていきましょう。

まず一覧用の固定ページを追加します。タイトルやスラッグは別になんでもいいですが、今回は「お気に入り一覧」というタイトルと「favorite」というスラッグで作成します。

出力用テンプレート「page-favorite.php」を作成して下記のような内容とします。

<?php get_header(); ?>

<main>
	<h1><?php echo esc_html(get_the_title()); ?></h1>
	<div id="favorite" class="favorite"></div>
</main>

<?php get_footer(); ?>

<div id=”favorite” class=”favorite”>の中にAjaxで生成した記事一覧が格納されるイメージです。

AjaxでWordpressの記事を読み込み

まずfunctions.phpに必要な処理を書いたjavascript(今回は「favorite.js」とする)を「wp_enqueue_script」で登録して、「wp_localize_script」で必要なデータを登録したjsに受け渡しできるようにします。

function favorite_ajax_scripts(){
	//お気に入り一覧ページのみ読み込み
	if(is_page('favorite')){
		wp_enqueue_script('favorite', get_theme_file_uri('favorite.js'), array('jquery'));
		wp_localize_script('favorite', 'favorite_params', array(
			'ajaxurl' => admin_url('admin-ajax.php')
		));
	}
}
add_action('wp_enqueue_scripts', 'favorite_ajax_scripts');

次に「favorite.js」の中身を書いていきます。

$(function(){
	//現在の登録内容を取得
	var favorite = JSON.parse(localStorage.getItem('favorite'));
	
	//コンマ区切りの1つの文字列にする
	var ids = favorite.join(',');
	
	//格納する囲み
	var list = $("#favorite");
	
	//WPに引き渡すデータ
	var data = {
		//アクション名(任意の名前)
		'action': 'favorite',
		//お気に入り登録している記事ID
		'id': ids
	};
	
	//Ajax処理
	$.ajax({
		url: favorite_params.ajaxurl,
		data: data,
		type: 'POST'
	}).then(
		//成功時
		function(data){
			if (data) {
				list.append(data);
			}
		},
		//失敗時
		function(){
			alert("読み込みに失敗しました");
		}
	);
});

まず最初にLocal Storageの登録しているお気に入り記事IDを取得して、それをコンマ区切りの1つの文字列に変換します。

次にWPから帰ってきた内容を格納するための囲みを定義しておいて、WP側に引き渡す情報をまとめます。引き渡すのはアクション名と先ほどのコンマ区切りのID文字列になります。
アクション名は任意のもので良いので分かり易い名前にしておきます。

Ajax通信が成功したら、戻ってきたデータを事前に定義していた囲みの中にappendで追加するという流れです。

最後にもう一度前述のfunctions.phpの中に記述を追加します。

add_action('wp_ajax_favorite', 'favorite_ajax_handler');
add_action('wp_ajax_nopriv_favorite', 'favorite_ajax_handler');//wp_ajax_nopriv_{アクション名} -> favorite.jsで指定したアクション名
function favorite_ajax_handler(){
	$post_ids = $_POST['id'];
	$post_ids = explode(',',$post_ids);
	$args = array(
		'posts_per_page'=>-1,
		'post__in'=>$post_ids
	);
	query_posts($args);
	if(have_posts()):
	echo '<ul class="archive">';
		while(have_posts()): the_post();
		?>
		<li>
			<h2><?php the_title(); ?></h2>
			<?php the_excerpt(); ?>
		</li>
		<?php
		endwhile;
	echo '</ul>';
	else:
	echo '<p>まだお気に入り登録がありません</p>';
	endif;
	die;
}

まずadd_actionで後述の処理をフックさせますが、アクション名の「wp_ajax_favorite」及び「wp_ajax_nopriv_favorite」の「favorite」部分は先ほどの「favorite.js」内で指定したアクション名と一致しておく必要があります。

POST送信でお気に入り登録している記事IDがfavorite.jsから届くので、それを今度はカンマ毎に配列に直して、「post__in」で必要な記事IDとして指定します。

あとは記事があればループ処理で適宜お好みの形で整形して出力させます。
これで「お気に入り一覧」ページにアクセスすると記事がAjaxで読み込まれるはずです。

あとは必要に応じてこの一覧にも登録解除ボタンを入れてみるのもいいですね。

最後に

Local Storageは扱いやすくて容量も豊富ですが、セキュリティ面では弱く、今現在データを保護することはできないらしいのでユーザーにとって重要な情報、例えば

  • ログインユーザーIDやパスワード
  • 個人情報
  • クレジットカード情報
  • APIキー

などそういった重要な情報は決してLocal Storageには保存しないようにしましょう。

ではまた次回。

ロリポップでOwnCloudを使ってみよう

$
0
0

こんにちは、子供が生まれてから活動時間を夜型から朝型にシフトしつつあるムラタです。

今回はロリポップが2020年6月17日から導入した「ownCloud」を、実際にインストールして使用してみるまでの流れや使用感をご紹介してみようと思います。

ownCloudとは

DropboxやGoogleDriveなどオンライン上でデータを保存・管理共有などが行える「オンラインストレージ」と呼ばれる仕組みを簡単に構築できるアプリケーションになります。

オンラインストレージを構築することでネットにさえ繋がっていれば、場所を選ばずストレージにアップしているデータにアクセスできますし、特定のユーザーとファイルの共有や、共有ユーザーを都度作成しなくとも共有URLを発行することで、URLを知っている人ならだれでも共有することも可能です。

容量について

ownCloudはロリポップのサーバーの中に構築するのでDropboxやGoogleDriveのように決まった容量があるわけではなく、自身が契約しているロリポップのプランの容量がそのまま使用できるわけです。

実際問題、今までいくつもサイト制作をロリポップで行ってきた中で、プラン内の容量を使い切ったことは無いんですよね。自分の記憶の中が正しければ半分も使ったことないかもしれません。

となると、ownCloudを使用できる一番安いプランとなるライトプランで、容量半分使っていたとしても25GBも割り当てられるわけです。

参考までに各種オンラインストレージサービスの無料版の容量は下記の通りです。

サービス名 容量
Dropbox 2GB
GoogleDrive 15GB
iCloud Drive 5GB
Amazon Cloud Drive 5GB

※ただしサービスによっては無料版でも容量をある程度増やすことができるものもあります。

インストール

簡単インストール

それでは早速インストールしてみましょう。
まずロリポップの管理画面にログインし、「サイト作成ツール」メニューより「ownCloud簡単インストール」を選択します。

インストール画面に移るので、まず冒頭の注意文をよく読んで、要件を満たしているか確認してください。

今回インストールするownCloudのバージョンは10.4.1ですが、PHPのバージョンが「7.3」でのみインストールを行えるようです。
また、同じ7.3でもCGI版モジュール版とでストレージに転送できるファイルの転送量が異なり、CGI版は最大20MB、モジュール版は100MBとなっていて、結構違いがでるので要注意です。

URLとデータベースの指定

要件を満たしている場合は次に進みましょう。
ストレージとして使用するURLを決定します。
すでに運用しているサイトにディレクトリを切ってそこを使用してもいいと思いますが、サブドメインを作ったほうが確実じゃないでしょうか。
というわけで今回は「https://owncloud.xxxxx.com」というサブドメインを新しく用意して「storage」というディレクトリを切って使用することにしました。
※ディレクトリを切らずにルートに入れようとしたところ、「フォルダ名と同じ名前のファイルが存在するため設置できません」というエラーでインストールできなかったので、何かしらディレクトリを切る必要があるようです。

使用するデータベースも聞かれますが、既存のものでもいいし、新規で自動作成してもいいです。
ここもownCloud専用のデータベースを新規で作成して使用します。

ownCloudのユーザー設定

次にownCloudで使用するユーザー設定を行います。
任意のユーザー名とパスワードを入力します。
ユーザー名に「admin、test、administrator、Admin、root」等の想定されやすい名前は使用できないので注意です。

内容に不備が無ければ「入力内容確認」ボタンを押すと確認画面に移ります。

確認画面

最終確認画面に移るので問題が無ければ「インストール」を押して実行します。

注釈にある通り、インストール完了までちょっとかかるので、あせらず終わるのを待ちましょう。

インストール完了

正常にインストールが完了するとURLと使用するデータベースが表示されるので、とりあえずURLの方は控えておきましょう。

ちなみにownCloudにバックアップ機能はデフォルトで備わっていないので、万が一を考えてバックアップを取りたい人はロリポップのバックアップオプションを申し込んでおきましょう。

ownCloudブラウザ版の使用感

ログイン画面

では早速先ほどのサイトURLをたたいてアクセスしてみましょう。

こんな感じのログイン画面に遷移するので先ほど設定してアカウント情報でログインします。

ログインが完了するとデスクトップアプリやスマホアプリを勧めてくるので、必要な場合はインストールしときましょう。
とりあえず今回はこのままブラウザベースで進めていきます。

マニュアル

ストレージのTOPにはデフォルトで「Documents」と「Photos」というフォルダと「ownCloud Manual.pdf」というマニュアルのPDFファイルが作成されています。

ちなみにマニュアルの中身は全て英語なので英語が苦手な方にはちょっとしんどいですね。
ownCloudの公式には「日本語マニュアルをご希望の皆様はこちら」というリンクがありますが、お問い合わせページに飛ばされるので、問い合わせたら日本語版のマニュアルが貰えるのかもしれないですね。

ただそんなマニュアルが必要なほど色々な機能があるわけでもなく、シンプルな感じなのでマニュアルなくても触ってたらだいたい分かるかなと思います。

ファイル

ちなみにDocumentsフォルダの中には「Example.odt」というファイルが入っていて、Photosの中には3枚jpg形式の写真がアップされています。

jpgやpng、gifなどの形式に関してはサムネイルも表示されますが、ちょっと小さすぎるような・・・この辺のサイズも変えれたらいいんですがブラウザ版ではそういったオプションは見つけられませんでした。

また上記画像形式に関してはクリックでその場で拡大展開して中身を確認することができます。

PDFやWordファイルに関しては残念ながらその場で中身を確認できずダウンロードするだけのようです。
ただ後述するアプリ版であればPDFはダウンロードすることなく閲覧可能なようです。

ファイルアップ

ファイルをアップする際はアップしたい階層へ移動してファイルをドラッグアンドドロップするだけです。(もしくはパンくず部分の「+」アイコンから「アップロード」からファイルを選択)

アップが始まると画面上部のパンくず部分におおよその残り時間と共にプログレスバーが表示されます。

コメント

アップされているファイルをクリックして選択した際、そのファイルに対してタグやコメントなどの情報を付与したり、共有の指定などができます。

ただ自分がコメントを設定した際、ページを更新したらなぜかコメントが消えてました。
現時点でうまく動いていないような気がします。

タグ

ファイルに対して任意のタグを設定することができます。

設定したタグに対してownCloudメニューの「タグ」より絞り込み検索ができる・・・と思ったのですが、ローディングアイコンがぐるぐるするだけで何も表示されることがなく、残念ながらこちらも動いていないようです。うーん・・・(そもそもの使い方が違うならごめんなさい)

共有

ユーザーおよびグループでの共有

ファイル共有には「ユーザー」指定か「グループ」指定、もしくはURLを知っている人ならだれでも共有できる「公開リンク」の3種が用意されているようです。

試しにユーザーでの共有をしてみましょう。
共有したいユーザー名を入力すると下に候補が出るので該当のユーザーをクリックして選択します。

共有したユーザーごとに細かい共有設定ができるらしく、いつまで共有を有効にするか、編集を許可するかなどが指定できます。(『編集を許可』と『変更』は何が違うんですかね?)

公開リンクでの共有

ユーザーやグループではなく、公開リンクを指定した場合、以下のようなダイアログが表示され、設定を行うことができます。

「ダウンロード/表示」というラジオボタンで恐らくダウンロードできるのかただファイルを見せるだけなのかを選べるんだと思うのですが、なぜかラジオボタンがひとつしかなく、そもそも選択することができません。
なにか拡張機能が必要なのか、ファイルによるのかもしれないですが今のところ不明です。

実際に公開リンクにアクセスすると以下のようなページでダウンロードを行うことが可能になります。

共有中のファイル一覧

ちなみに現在共有設定をしているファイルに関してはメニューの「他ユーザーと共有中」から確認することができます。
ユーザーなのかグループなのか、公開リンクなのか一目で分かるようになっています。

お気に入り

ファイルに対して「お気に入り」を付けて、メニューからまとめて見れるっぽいのですが、なぜかこれもローディングアイコンがぐるぐる回るだけで何も表示されませんでした。

ブラウザ版の基本的な操作に関してはこれで以上かと思います。
他にも「設定」からいろいろできそうなのですが今回は割愛します。

スマホアプリからアクセス

ownCloudには前述したとおり、ブラウザ版以外にもデスクトップアプリやスマホアプリなどがあります。
今回はその中からiosのアプリを使ってアクセスしてみます。

アプリダウンロード

App Storeで「ownCloud」と検索すると「ownCloud – with legacy support」と「ownCloud – File Sync and Share」の2つが上位に出てくると思いますが、最初に出てくる「with legacy suuport」の方は有料で¥120かかります。(2020年6月25日現在)

この2つでどう機能面に違いがあるのかよく分からなかったのですが、ロリポップのマニュアルで「スマートフォンアプリは有料です」と記載があるものの、その下のQRコードからアクセスしたアプリは後者の無料版の方でした。

アカウントの追加

アプリを起動させるとまずアカウントの設定が始まります。
サーバーURLを求められるので、ロリポップの簡単インストールで指定したURLを指定しましょう。

次に使用するアカウント情報を求められるので「CREDENTIALS」に、ユーザー名とそのパスワードを入力します。

アカウント情報に不備が無ければアカウントが無事追加され、アカウント一覧画面に遷移します。

先ほど追加したアカウントをタップしてサーバーにアクセスします。

問題なければ先ほどのブラウザ版と同じファイルが表示されると思います。
これでスマホアプリでのアクセスは完了です。

最後に

コメントやお気に入りなどの一部機能の動作が怪しかったですが、基本的なファイル共有だけであれば問題なく使用できそうでしたね。

容量だけで見たら契約しているロリポップのプランと使用状況にもよりますが、他のストレージサービスの無料版に比べて結構多めのストレージを確保できるんじゃないかなと思います。

それに今回触れませんでしたが、アドオンを用いて色々な機能を追加したり、オープンソースなので知識があれば自分でもっと自由にカスタマイズすることもできるのは他のサービスにはない一番の強みだと思います。

ただバックアップに関しては別途ロリポップのオプションを導入する必要がありますし、セキュリティについては他と比べて自前のサーバーに構築するということから、どうしても不安に感じてしまいますね。

以上ロリポップでownCloudを使ってみた話でした。

gulp + Pug で静的ページコーディング その2

$
0
0

どうも、ヨドバシのPS5予約抽選に落ちて絶望しているムラタです。

以前ご紹介した「Pug」ですが、意外にもあれから何度か使う機会があり、前回知らなかった使い方や便利な記述方法などを知ることができたので、それらをご紹介したいと思います。

Pugの基本的な使い方やgulpでのPugの使い方については前回の記事を参照してください。

gulp + Pug で静的ページコーディング

ファイル構成

最近自分がpugを使用する場合、以下のようなファイル構成で構築を行っています。

include」は前回の記事でも紹介したinstall機能で使用するヘッダーやフッターなどほとんどのページで共通して使用するパーツになります。

layout」は前回では「_layout.pug」として単一のファイルとしていましたが、TOPと中ページなどレイアウトが結構変わることがほとんどなので、TOP用の「_top.pug」と中ページ用の「_page.pug」の2つに分けています。
「if」などの分岐で済むようなちょっとした差分しかないのであれば、場合によっては1つでもいいかもですね。

mixin」には使いまわすブロックをそれぞれファイルで分岐して格納しています。

そして今回は「pug」ディレクトリ直下に「_config.pug」というファイルを用意しています。
「config」ディレクトリを切ってもいいかもですね。

configファイル

このファイルの中に何が記述されているかというと、「サイトタイトル」や「ルートパス」、「cssやimgなどの各ディレクトリへのパス」といった、サイト全体で使用する情報を変数で格納しています。

自分は下記のような感じで記述しています。
通常の変数と判別がつきやすいように全て大文字で定義するようにしています。

-
	//- サイトタイトル
	var SITE_TITLE = 'サイトタイトル名';
	
	//- ページタイトルとサイトタイトルのセパレート
	var SITE_TITLE_SEPARATO = '|';
	
	//- ルートパス
	var ROOT_PATH = '/';
	
	//- 画像ディレクトリパス
	var IMG_PATH = ROOT_PATH + 'assets/img/';
	
	//- CSSディレクトリパス
	var CSS_PATH = ROOT_PATH + 'assets/css/';
	
	//- JSディレクトリパス
	var JS_PATH = ROOT_PATH + 'assets/js/';

そしてこの_config.pugをlayoutファイル内の冒頭でincludeして使用しています。
例えば下記のようになります。

block config
	include ../_config
	include ../mixin/_pankuzu
doctype html
html(lang="ja")
	head
		block head
			include ../include/_head
		block preload
		block css
	以下略

例えば_head.pug内で以下のような形で使用します。

if page.title != ''
	title #{page.title}#{SITE_TITLE_SEPARATO}#{SITE_TITLE}
else
	title #{SITE_TITLE}

各ページファイルで連想配列でページタイトルやdescription、各種OGP情報等ページに関わる情報を格納しているんですが、上記はページタイトルの有無でTOPかそれ以外かで出力させる内容を分岐させています。

TOPであれば「{サイト名}」だけの出力で、それ以外は「{ページタイトル} – {サイト名}」となります。

三項演算子による分岐

例えばある変数の値によってclass名を分岐させたいとき、if分を使って分岐させるのが一般的ですが、その場合以下のように記述します。

if position == 'hight'
	<p class="hight">テキストテキスト</p>
else
	<p class="normal">テキストテキスト</p>

この場合、pタグではなくdivタグに変更になった際や、中身のテキストが変わった際に2回修正をかける必要があり、なんだかスマートじゃないですね。

そういった時に三項演算子を使うと簡潔に書くことができます。

p(class = position == 'hight' ? 'hight' : 'normal') テキストテキスト

スマートになりましたね。

class等の属性ではなくテキストを変えたいときは以下のようにします。

p #{(position == 'hight') ? "Hight" : "Normal"}

複数のクラス名を配列でまとめて指定

2つ以上のクラスを指定する場合、配列でまとめて指定することもできます。

tr(class = ['reserve-timetable__status','reserve-timetable__status--end'])

これで「<tr class=”reserve-timetable__status reserve-timetable__status–end”>」と出力されます。

インラインスタイルを連想配列で指定

style属性を用いてインラインスタイルを適用する場合、連想配列で指定することができます。

div(style={background: '#ff0000', font-size: '2rem'})

エスケープ処理

下記の記述でテキストの内容をエスケープ処理して出力することができます。

p
	= '自動でエスケープ処理されます。<a>エスケープ処理される</a>'

エスケープ処理したいテキストを「=」で指定することで下記のように出力されます。

<p>自動でエスケープ処理されます。&lt;a&gt;エスケープ処理される&lt;/a&gt;</p>

逆にエスケープ処理してほしくないときは

p
	!= '自動でエスケープ処理されます。<a>エスケープ処理される</a>'

「!=」とすることでエスケープ処理されずにそのまま出力されます。

ifとunless

unrellはifの結果が偽の時と同じ処理になります。

unless check
	p チェックされていません
if !check
	p チェックされていません

上記2つの処理は同じということです。
まあ別にどっち使ってもいいんじゃないかと思うんですけど、unlessの方が馴染みが無いので普通にifでいいですね。

最後に

今回はちょっと短めですが、今後また新しい発見やもっと便利な使い方が見つかるかも知れないのでその時はまたご紹介します!

Viewing all 33 articles
Browse latest View live