ただいま新デザインバージョンのプレビュー中です。旧バージョンはこちら。不具合等はお問い合わせより報告お願いいたします。

メルマガ全文サンプル

2015 年 11 月 5 日に発行された、CodeGrid 173 号のメルマガ全文のサンプルです。

CodeGrid 173号 (2015年11月5日発行) フロントエンドに関わる人々のガイド https://www.codegrid.net/ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ このメールはCodeGridの購読手続きをしていただいた方へ配信しています。 ――――――――――――――――― 【 目次 】 ――――――――――――――――― 【 読み物 】 - ピクセルグリッドの仕事術(18):経理業務フローの効率化 https://app.codegrid.net/entry/px-works_kaikei ピクセルグリッドでは、専任の経理スタッフを社内に雇わず、社長である中村が経理業務作業を行っています。どのように効率的に行っているか、その経理業務フローを紹介します。 (フロントエンド・エンジニア 中村 享介) 【 技術情報 】 - 静的HTMLのためのテンプレートエンジン(2):HandlebarsとECT https://app.codegrid.net/entry/template-for-coding-2 静的なHTMLを作成するためのテンプレートエンジンの中からHandlebarsを取り上げ、その使い方とメリット・デメリットについてじっくりと解説します。また、ECTも紹介します。 (フロントエンド・エンジニア 坂巻 翔大郎) 【 技術情報 】 - 実践、jQuery(6):位置とサイズの取得 2 https://app.codegrid.net/entry/jquery-position-2 offset()メソッドを利用し、html要素をオフセット要素とした相対的位置の取得、また位置の設定を解説します。シンプルな例だけでなく、疑似フレーム内に要素があった場合の挙動についても扱います。 (フロントエンド・エンジニア 森 大典) ================= 【 読み物 】 ピクセルグリッドの仕事術(18):経理業務フローの効率化 ================= ピクセルグリッドでは、専任の経理スタッフを社内に雇わず、社長である中村が経理業務作業を行っています。どのように効率的に行っているか、その経理業務フローを紹介します。 (フロントエンド・エンジニア 中村 享介) ◆アプリURL◆ https://app.codegrid.net/entry/px-works_kaikei ◆目次◆ - 経理業務フローの問題 - 銀行の口座情報の管理 - 見積もりの分散と請求書処理の効率化 - Webのアシスタントサービスを使う - まとめ 会社という組織である以上、ピクセルグリッドもきちんと会計を行っています。しかし、それを専門に行っているスタッフはいません。 知り合いの経営者からは早めに経理スタッフを入れるといいよといったアドバイスももらいました。しかし、悩んだ末、そういったスタッフは入れないという判断をしました。今回は、どのように考えてそういったスタッフを入れないことにしたのか、また、どのようなフローにすることで経理業務に対応しているのかをご紹介します。 ――――――――――――――――― 経理業務フローの問題 ――――――――――――――――― 最初にお話ししたようにピクセルグリッドのメンバーに管理・経理スタッフはいません。税金を計算するといった、決算のための会計はすべて税理士さんにお任せしています。とはいえ、税理士さんに渡す書類を整理したり、振り込み処理をしたりといったことは、誰かがやらなければなりません。誰がやるのでしょうか?  現在は、それはすべて社長である私が作業することにしています。こういった処理を私が行うことは、効率が悪いことを認識した上での選択です。 会社設立当初の経理作業はこのような形でした。枠で囲まれた部分が私の作業です。 /* 図:ピクセルグリッドの設立当初の経理作業の流れ */ https://app.codegrid.net/s3/pix-works/img/first.png 単純にスタッフを雇えば、枠で囲まれた部分、たとえば領収書整理や通帳記帳、請求書発行といった作業の負担は減るでしょう。私はどちらかというと、こういった事務処理的な作業は苦手です。多分、普通の人よりも時間がかかります。 しかし、各種Webサービスを使ったり必要なプログラムを書いたりして、より効率的にする方法を考えることは得意です。しばらくこのような作業をしていると、もっと効率良く、楽にできる方法はないか、といったことを考え始めます。気が付くといろいろな方法を試し、導入していました。 自分の作業が減るような方法を導入し続けていくことで、会社の成長とともに作業が増えても、メインの仕事の合間だけで対応することができるのです。 //// 経理スタッフがいるメリット 知り合いの経営者から、自社で経理スタッフがいるメリットを教えてもらったことがありましたが、その一つとしてキャッシュフローを把握しやすいということでした。 会社は、会計上黒字だったとしても、現金がなくなり、支払いができなくなれば倒産します。社内で経理を行うスタッフがいれば、いつキャッシュがつきそうなのか、数ヶ月先まで計算しておくことで予測することができます。 ピクセルグリッドでは専門のスタッフがおらず、キャッシュフローの把握が遅くなりがちな分、多めにキャッシュのバッファを持つことで対応しています。 //// ――――――――――――――――― 銀行の口座情報の管理 ――――――――――――――――― 銀行の口座の管理は、通帳にきちんと記帳し、そのコピーを税理士さんへ渡すだけというフローになっていました。しかし、銀行口座や取引が増えるにつれ、記帳のし忘れや、合算されてしまって通帳だけでは明細が出せないといったことが起こりました。また、各種銀行のサービスは利用不可の時間があったりと少し不便です。 参考:各種銀行の法人向けサービス みずほビジネスWEB https://www.mizuhobank.co.jp/corporate/ebservice/account/b_web/index.html * 利用料金(ライトプランの場合)は月額基本料金 2,160円(消費税等を含む) * 当日を含め、23営業日間の取引明細を照会 * 残高・入出金明細照会利用時間は平日8:00~23:00(土日はさらに短い) * 法人専用のUIで見た目が残念、使いづらい ゆうちょダイレクト http://www.jp-bank.japanpost.jp/direct/pc/dr_pc_index.html * 各種照会に関しては月額料金なし * 通帳未記入が30行を超えた場合、明細が合算される * 利用時間は0:05~23:55(第1、3月曜日0:00〜6:30は振替受払通知症の照会は利用不可) * 個人のUIと共通で、使い勝手はほどほど そこで、会計ソフト(サービス)の「freee(#1)」を利用して、銀行口座の情報をfreeeに集めるようにしました。自分でプログラムを書いて、スクレイピングするという方法も考えたのですが、各銀行ごとに用意するのは工数がかかってしまいますし、セキュリティの問題もあります。その点、freeeであれば多くの銀行に対応している上、もし銀行のサイトがアップデートされてもfreeeの方で対応してくれるのも良い点です。 #1: https://www.freee.co.jp/ また、freeeに税理士さんのアカウントを作って、その集めた口座の情報を直接参照できるようにしています。これにより、通帳のコピーを渡さなくても口座の情報が見ることができ、いつでも最新の口座情報を参照できます。freeeの役割は取り込んだ情報を管理することであり、クラウド上からの振り込み機能はないので、その辺も安心できます。 さらに、エンジニアとしては、WebのAPIが充実しているところも便利だと思います。特定の振り込みを検出したらメールを送るといった仕組みも作ることができます(実際運用しています)。 なお、freeeは会計ソフトですが、ピクセルグリッドでは現時点では会計は行わず、銀行口座の情報管理に留めています。しかし、税理士さんとも相談し、今後会計で使えそうかといった実験を進める予定です。うまくいけば税理士さん側で作成してもらった会計情報にいつでもアクセスできるようになるので、それを経営にも活かせるといいなと思ってます。 ――――――――――――――――― 見積もりの分散と請求書処理の効率化 ――――――――――――――――― 見積書・請求書について、当初は「OpenOffice」を使ったり「Pages」を使ってみたりと、ローカルのアプリケーションで対応していたのですが、こちらも取引が増えるにつれ管理しきれなくなっていきました。特に見積もりを作成するためには、プロジェクトを把握しなければならず、1人で行うには厳しくなっていました。 そこで、見積もりに関して複数のスタッフで利用できるサービスで何か良いものがないかと探し、現在は「MakeLeaps(#1)」で管理しています。MakeLeapsでは複数のユーザーで管理が可能なので、見積もりを発行する可能性がある社内のスタッフをユーザーとして登録し、それぞれに見積書を作ってもらっています。 #1: https://www.makeleaps.jp/ // *注:社内の見積もり方法について 社内の見積もり方法については、この連載の第1回「JavaScript開発の見積もり(#1)」、第2回「ポイントによる見積もり(#2)」で紹介しています。参考にしてください。 #1: https://app.codegrid.net/entry/general-estimate #2: https://app.codegrid.net/entry/point-estimate // これにより、作ってもらった見積書を必要に応じて確認するだけで社内で見積書が発行できるようになりました。 見積もり以外にも、請求書をMakeLeapsで管理するようにしたことで、税理士さんに直接請求書を取得してもらうことができるようになりました。この作業も、これまでは発行請求書のコピーを揃える必要があったので、楽になった部分です。 その他、ざっくりとした売上予測のためのスプレッドシートを手動で管理していたのですが、MakeLeapsのホーム画面にあるグラフを活用することで不要になりました。請求時期が先になりそうなものでも未来の日付で仮に請求書を作っておくことで、グラフに反映され、大体の売上がわかるようになります。未来の日付で作っておくと、請求書の送り忘れを防止できるという点も便利です。 また、可能な限り郵送ではなくPDFで請求書を送付することで、送付についても効率化を図っています。 ――――――――――――――――― Webのアシスタントサービスを使う ――――――――――――――――― ** アシスタントの必要性 会社では、逆に請求書を受け取ることも多くなります。そのように受け取った請求書はPDFであったり紙で郵送されてきたりと、バラバラです。さらに、それらの請求のうち、どれが支払い済みなのか、振り込み漏れがないかなと細かくチェックする必要があります。 このような作業は人が作業した方が早いことが多くなります。とはいえ、作業が増えてくるとミスも起こりやすくなりますし、担当するスタッフが欲しくなります。 そこで、ピクセルグリッドでは「Caster.biz(#1)」というオンラインアシスタントサービスを導入しました。オンラインでのやり取りになるので、紙で受け取った請求書はスキャンしてPDFにする手間がありますが、普段使っているSlackでコミュニケーションができ、気軽に作業を依頼することができます。 #1: https://cast-er.com/ 請求書の管理以外にも、社内の経費精算をまとめたり、freeeにアクセスして入金を確認してもらったりといったこともお願いしています。 ** オンラインアシスタントのメリット 社内に1名、経理・管理スタッフを雇うのに比べると、オンラインアシスタントは月の契約時間で月額が決まり、稼働に合わせて費用を払えばよいので会社が負担するコストとしては安く済みます。また、担当するスタッフがおやすみの際、代理のスタッフに担当してもらえるというのも大きな利点です。 リモートワークを導入しているピクセルグリッドでは出社する義務はないので、社員がオフィスにいるとは限りません。そうなるとやりとりは必然的にSlackになり、オンラインで対応してもらえることで助かっています。管理スタッフをリモートワークにするのであれば、最初からオンラインアシスタントを検討するのは良さそうです。 もちろん、オフィスで書類の整理をするといったことはお願いできませんが、できる限り紙の書類を排除していけるのであればメリットの方が大きいでしょう。 ――――――――――――――――― まとめ ――――――――――――――――― ここまで紹介してきた工夫で、私の経理作業は次のようになりました。枠で囲まれた部分、「領収書整理」や「PDF化/転送」は作業部分ですが、それ以外の部分を各サービスに任せることができています。会社の規模としては設立当初より大きくなっていますが、経理業務フローに関しては、体感としても私の負担は増えたようには感じていません。 /* 図:ピクセルグリッドの現在の経理業務の流れ */ https://app.codegrid.net/s3/pix-works/img/current.png 特に負荷が大きい部分を見極めて最適化していくのは、パフォーマンスチューニングみたいなものです。いろいろ試してきて、特にボトルネックになりやすいと感じるのは次の2つです。 * 紙でやり取りしている部分 * エクセルで管理している部分 どうしても紙でないといけないものだったとしても、スキャンしてデータ化するしてやり取りできるようにするだけでスキャンするコスト以上に効果があります。また、エクセル(Googleスプレッドシートも含む)で管理している内容に対応したWebサービスがあれば、お金がかかったとしても管理コストが大きく下がる可能性が高いでしょう。 // 電子帳簿保存法の改正 2015年度の税制改正により「電子帳簿保存法」が改正されました。領収書の電子保存が利用しやすくなるなど、少しずつ紙でなくてもよいものが増えてきています。 // たとえば今回紹介したように、紙の領収書をスキャンしてデータ化することでオンラインアシスタントサービスを利用して管理したり、税理士さんへオンライン経由で送ったりすることができます。スキャンの手間とアシスタントサービスのコストはかかりますが、それに見合う効果があったわけです。 ピクセルグリッドでは、今後もコストとその効果を考え、最適化の工夫を続けたいと思います。 ================= 【 技術情報 】 静的HTMLのためのテンプレートエンジン(2):HandlebarsとECT ================= 静的なHTMLを作成するためのテンプレートエンジンの中からHandlebarsを取り上げ、その使い方とメリット・デメリットについてじっくりと解説します。また、ECTも紹介します。 (フロントエンド・エンジニア 坂巻 翔大郎) ◆アプリURL◆ https://app.codegrid.net/entry/template-for-coding-2 ◆目次◆ - 各テンプレートエンジンの特徴 - Handlebarsを実行する - Handlebarsのテンプレートに値を渡す - Handlebarsのインクルードと継承 - Handlebarsを使うメリット・デメリット - ECTの特徴と現状 - ここまでのまとめ 前回は、効率的にHTMLを作成していくために筆者がこれまでとってきた手法と、今回の記事で扱うテンプレートエンジンに共通するインクルード・継承といった機能について解説しました。 今回は、より効率的に静的なHTMLを作成するためのテンプレートエンジンにどういうものがあるのかを紹介し、それぞれの使い方とメリットデメリットについてをこれから2回に分けて解説していきます。 ――――――――――――――――― 各テンプレートエンジンの特徴 ――――――――――――――――― このシリーズで取り扱うテンプレートエンジンは次の3種類です。それぞれの特徴を表にまとめました。 テンプレートエンジン:継承:インクルード:特徴 Handlebars:△:◯:Helperを利用すれば継承が可能。元々できることが少ないが、独自に拡張が可能で、プラグインも多い。 ECT:◯:◯:ビルドが高速(らしい)(#1)。テンプレートの中で、CoffeeScriptの構文を扱うことができる。 Jade:◯:◯:DOM構造の入れ子をインデントで表現する記法。 #1: http://ectjs.com/#benchmark 今回はこれらの中からHandlebarsを中心に解説し、ECTについても簡単に触れてみます。 ――――――――――――――――― Handlebarsを実行する ――――――――――――――――― Handlebars(#1)は、Mustache(#2)を拡張したテンプレートエンジンです。クライアントのJavaScriptや、Node.js上でも利用することができます。なお、Handlebarsを使用してHTMLを描画する方法については、CodeGridの記事で高津戸が「テンプレートエンジンのススメ Handlebars(#3)」の中で解説しているので、Handlebarsに興味を持ったら参照してみてください。 #1: http://handlebarsjs.com/ #2: https://mustache.github.io/ #3: https://app.codegrid.net/entry/handlebars 冒頭で記述したように、Handlebars自体には、別々に分けたファイルを読み込むようなインクルード機能や、テンプレートの継承といった機能がありません。しかし、HandlebarsはHelperというテンプレートのカスタマイズ機能のようなものを備えているので、それらを利用するとインクルードやテンプレートの継承をすることが可能です。 小難しい話は置いておいて、まずはサンプルを見てください。Handlerbarsをgulpから使用して実際にHTMLを生成できるサンプルです。次のリポジトリからダウンロード、またはクローンできます。 //// Handlebarsサンプルリポジトリ * https://github.com/codegrid/2015-build-html/tree/master/demo/handlebars https://github.com/codegrid/2015-build-html/tree/master/demo/handlebars //// Handlebarsを使用した今回のサンプルのディレクトリ構成は、次のようになっています。 pre.code | handlebars/ | ┣ package.json | ┣ gulpfile.js | ┣ src/ | ┃ ┣ pages/ # 各ページのHTMLとなるファイル | ┃ ┃ ┣ page0.hbs | ┃ ┃ ┣ page1.hbs | ┃ ┃ ┗ page2.hbs | ┃ ┗ partials/ # 継承する雛形や、インクルード | ┃ ┣ meta.hbs # meta要素など共通部分 | ┃ ┣ header.hbs # 共通ヘッダー | ┃ ┣ footer.hbs # 共通フッター | ┃ ┗ layout.hbs # サイト共通の雛形 | ┗ htdocs/ gulpからHandlebarsを扱うには、gulp-hb(#1)を使用します。大本のHandlebars(#2)では、ファイルを読み込む形式のインクルードの機能がないのですが、gulp-hbでは、分割したファイルを読み込むことができます。また、テンプレートの継承をするためのヘルパーとしてhandlebars-layouts(#3)を使用しています。 #1: https://www.npmjs.com/package/gulp-hb #2: http://handlebarsjs.com/ #3: https://www.npmjs.com/package/handlebars-layouts 必要なモジュールは、package.jsonに記述してあるので、demo/handlebars/ディレクトリに移動して、npm installを実行すれば必要なモジュールがインストールされます。 また、gulpfile.jsの中では、Handlebarsのテンプレートファイル(拡張子がhbsのもの)の場所や、出力するファイルの拡張子、出力されるディレクトリなどを指定しています。 pre.code.javascript | var gulp = require('gulp'); | var rename = require('gulp-rename'); | var hb = require('gulp-hb'); | | gulp.task('html', function() { | //各ページとなるファイルの場所を指定する | return gulp.src('./src/pages/**/*.hbs') | .pipe(hb({ | data: { | //テンプレート内で利用できるデータ | sitename: 'My Website', | list : ['りんご', 'ごりら', 'ライオン'] | }, | helpers: [ | //継承を可能にするヘルパーを指定する | './node_modules/handlebars-layouts/dist/handlebars-layouts.js' | ], | //インクルードや継承するための雛形の設置場所を指定する | partials: './src/partials/**/*.hbs', | bustCache: true | })) | //拡張子をhbsからhtmlに変更する | .pipe(rename(function(path) { | path.extname = '.html'; | })) | //出力先を選択する | .pipe(gulp.dest('./htdocs')); | }); demo/handlebars/ディレクトリに移動し、npm installをしてモジュールをインストールしたあとに、npm run buildと実行します。すると、srcディレクトリ以下のhbsファイルを元にhtdocsディレクトリに、HTMLが生成されます。 次にHandlebarsでできることを見ていきましょう。 ――――――――――――――――― Handlebarsのテンプレートに値を渡す ――――――――――――――――― Handlebarsのテンプレートの中で利用したいデータがある場合は、gulpfile.jsのオプションで渡すことができます。gulpfile.js内でテンプレートに値を渡している部分は以下の箇所です。 pre.code.javascript | /* ...省略... */ | .pipe(hb({ | data: { | //テンプレート内で利用できるデータ | sitename: 'My website', | author: { | firstName: 'Shotaro', | lastName: 'Sakamaki' | }, | list : ['りんご', 'ごりら', 'ライオン'] | }, | /* ...省略... */ 各テンプレートでは、ここで指定した値を受け取り、Handlebarsのテンプレート構文を利用して書き出すことができます。文字列が指定されたsitenameとオブジェクトが指定されたauthor、配列が指定されたlistを、テンプレートで展開しているsrc/pages/page0.hbsは次のとおりです。 pre.code.hbs | <h1>{{ sitename }}</h1> | | <p>姓は<b>{{ author.lastName }}</b>、名は<b>{{ author.firstName }}</b>です。</p> | | <ul> | {{#each list}} | <li>{{this}}</li> | {{/each}} | </ul> 文字列などの値を展開するためには{{ sitename }}のように記述し、配列を展開するためには{{#each list}}...{{/each}}と記述します。このsrc/pages/page0.hbsから生成したHTMLは次のとおりです。 pre.code.html | <h1>My website</h1> | | <p>姓は<b>Sakamaki</b>、名は<b>Shotaro</b>です。</p> | | <ul> | <li>りんご</li> | <li>ごりら</li> | <li>ライオン</li> | </ul> サイト共通の情報などを、一括で管理したい場合などは、このような形でテンプレートに値を渡す方法を使うと良いでしょう。 ――――――――――――――――― Handlebarsのインクルードと継承 ――――――――――――――――― デモで使用しているHandlebarsのテンプレートファイルの中身を見ていきます。まずは各ページのHTMLで継承するための雛形であるsrc/partials/layout.hbsです。コードは次のようになっています。 pre.code.hbs | <!doctype html> | <html class="no-js" lang="ja"> | <head> | {{> meta}} | | {{#block "css"}} | <link rel="stylesheet" href="/common/css/style.min.css"> | {{/block}} | | {{#block "js"}} | <script src="/common/js/lib/jquery-2.1.4.min.js"></script> | {{/block}} | | <title>{{#block "title"}}{{/block}} | {{ sitename }}</title> | </head> | <body> | {{#block "header"}} | {{> header }} | {{/block}} | | {{#block "body"}} | {{/block}} | | {{#block "footer"}} | {{> footer }} | {{/block}} | </body> | </html> ** インクルード まず、gulp-hbを使用するときの、インクルードの記述は{{> ファイル名}}のようにします。 pre.code.hbs | <html class="no-js" lang="ja"> | <head> | {{> meta}} こうすることで、src/partials/meta.hbsの内容がその箇所に展開されるようになります。src/partials/meta.hbsの内容は以下のとおりです。 pre.code.hbs | <meta charset="UTF-8"> | {{#if viewport}} | <meta name="viewport" content="width={{viewport}}, user-scalable=yes"> | {{else}} | <meta name="viewport" content="width=1030, user-scalable=yes"> | {{/if}} | <meta name="format-detection" content="email=no"> | <meta name="format-detection" content="telephone=no"> | <meta http-equiv="X-UA-Compatible" content="IE=edge"> そして、{{> header}}としている部分であればsrc/partials/header.hbsを、{{> footer}}としている部分はsrc/partials/footer.hbsの内容が展開されます。また、インクルードに値を渡したい場合は、{{> meta viewport="900" }}のように、インクルードファイル名のあとに、渡したい名前と値(ここではviewport="900")を指定します。 src/partials/meta.hbsの中で、その値を受け取っている部分は以下です。 pre.code.hbs | {{#if viewport}} | <meta name="viewport" content="width={{viewport}}, user-scalable=yes"> | {{else}} | <meta name="viewport" content="width=1030, user-scalable=yes"> | {{/if}} {{#if 条件}}...{{else}}...{{/if}}というHandlebarsのif文を使用し、もしインクルードに渡ってきた値にviewportがあればその値を使用する、というようになっています。こうすることで、同じインクルードでも、インクルードに渡す値を変えることで異なる結果を展開することができます。 ** 継承 次に、handlebars-layoutsを使用するときの、テンプレートの継承についてです。継承されるテンプレート内でブロックを宣言するには、{{#block "ブロック名"}}...{{/block}}という構文を使用します。 そして、そのテンプレートを継承する、src/pages/page1.hbsでは次のようにしています。 pre.code.hbs | {{#extend "layout"}} | | {{#content "title"}}ページ1です。{{/content}} | | {{#content "body"}} | <div>ページ1です。</div> | {{/content}} | | {{/extend}} src/pages/page1.hbsでは、各ブロックの上書きを行っています。そのsrc/pages/page1.hbsから生成したHTML(htdocs/page1.html)は以下のとおりです。 pre.code.html | <!doctype html> | <html class="no-js" lang="ja"> | <head> | <meta charset="UTF-8"> | <meta name="viewport" content="width=1030, user-scalable=yes"> | <meta name="format-detection" content="email=no"> | <meta name="format-detection" content="telephone=no"> | <meta http-equiv="X-UA-Compatible" content="IE=edge"> | | <link rel="stylesheet" href="/common/css/style.min.css"> | | <script src="/common/js/lib/jquery-2.1.4.min.js"></script> | | <title>ページ1です。 | My website</title> | </head> | <body> | <div class="header"> | <h1>ヘッダー</h1> | </div> | | <div>ページ1です。</div> | | <div class="footer"> | <small>フッターです</small> | </div> | </body> | </html> 雛形であるsrc/partials/layout.hbsに、src/partials/meta.hbsなどのインクルードが展開され、その雛形を継承したsrc/pages/page1.hbsの各ブロックの内容が展開されたHTMLが生成されました。各ブロックでは雛形のブロックを上書きする指定をしているため、もともと雛形の各ブロックに記述してあった部分はすべて上書きされています。 次に、src/pages/page2.hbsでは、ブロックの冒頭と末尾への挿入を行っています。 pre.code.hbs | {{#extend "layout"}} | | {{#content "title"}}ページ2です。{{/content}} | | {{#content "css" mode="append"}} | <link rel="stylesheet" href="/common/css/page2.css"> | {{/content}} | | {{#content "js" mode="prepend"}} | <script src="/common/js/lib/modernizr.custom.js"></script> | {{/content}} | | {{#content "js" mode="append"}} | <script src="/common/js/jquery.my-plugin.min.js"></script> | {{/content}} | | {{#content "body"}} | <h1>ページ2です。</h1> | <p> | このページは、ページ2です。<br> | </p> | {{/content}} | | {{/extend}} このsrc/pages/page2.hbsから生成したHTML(htdocs/page2.html)は次のとおりです。 pre.code.html | <!doctype html> | <html class="no-js" lang="ja"> | <head> | <meta charset="UTF-8"> | <meta name="viewport" content="width=1030, user-scalable=yes"> | <meta name="format-detection" content="email=no"> | <meta name="format-detection" content="telephone=no"> | <meta http-equiv="X-UA-Compatible" content="IE=edge"> | | <link rel="stylesheet" href="/common/css/style.min.css"> | <link rel="stylesheet" href="/common/css/page2.css"> | | <script src="/common/js/lib/modernizr.custom.js"></script> | <script src="/common/js/lib/jquery-2.1.4.min.js"></script> | <script src="/common/js/jquery.my-plugin.min.js"></script> | | <title>ページ2です。 | My website</title> | </head> | <body> | <div class="header"> | <h1>ヘッダー</h1> | </div> | | <h1>ページ2です。</h1> | <p> | このページは、ページ2です。<br> | </p> | | <div class="footer"> | <small>フッターです</small> | </div> | </body> | </html> {{#content "css" mode="append"}}...{{/content}}や{{#content "js" mode="prepend"}}...{{/content}}で、そのブロックの末尾や先頭に、CSSやJSが追記されているのが確認できます。こうすることで、あるページだけはCSSやJSを追記するといったことが可能になり、より柔軟にページを作成していくことができます。 ――――――――――――――――― Handlebarsを使うメリット・デメリット ――――――――――――――――― ここまで、Handlebarsの使い方とその機能を見てきましたが、メリット・デメリットを考えてみましょう。まず、メリットを挙げます。 * 書き慣れたHTMLの中に独自のテンプレート構文を混ぜられるため、学習コストが低い * 今回利用したhandlebars-layouts(#1)のように、独自にカスタマイズが可能で、情報も豊富 * クライアントサイド、サーバーサイドで動作するため、HTMLをサーバサイドで生成することも可能 #1: https://www.npmjs.com/package/handlebars-layouts 次にデメリットです。 * テンプレート構文が少ないため、テンプレート内で複雑な条件分岐などをしたい場合は、独自で拡張する必要がある Handlebars自身はあまりできることが多くありません。ですが、今回紹介したインクルードと継承を使うだけでも、効率的に静的なHTMLを制作できます。実際に、筆者が関わったプロジェクトではインクルードと継承の機能だけで済んでいます。 ――――――――――――――――― ECTの特徴と現状 ――――――――――――――――― ** ECTのデメリット 今回解説するもう一つのテンプレートエンジンであるECTは、売り文句である「Fastest JavaScript template engine」にあるように、筆者自身はHandlebarsを使ったときよりも、HTMLの生成が高速だと感じました。 ECTもHandlebarsと同様にインクルードや継承をすることができます。Handlebarsでの書式は{{ ... }}という形になっていましたが、ECTは<% ... %>という書式でテンプレートに記述していきます。Handlebarsの書式が理解できれば、ECTの書式で書かれたテンプレートも理解しやすいでしょう。 しかし、実は筆者はECTを今から採用することはあまりおすすめしません。その一番の理由は、開発が活発ではない点にあります。多少なり不具合がある状態で2年ほど動きがありません。 また、機能的なことで言えば、Handlebarsのように、あるブロック内の先頭や末尾に挿入することができず、上書きのみというデメリットもあります。 一度テンプレートエンジンを採用すると、テンプレート構文などの違いによって、他のテンプレートエンジンに乗り換えることが難しくなります。あまり複雑な使い方をしていなければ、手作業であったり機械的な置換で他のテンプレートエンジンに移行できる可能性もありますが、移行の際は何かしらのコストがかかるはずです。幸い、筆者が現在使用しているプロジェクトでは、あまり複雑な使い方はしていませんし、それによって不都合を感じる部分がないため、現状維持でECTを使用しています。 ですから、テンプレートエンジンを採用する際は、それに関してどれくらいの情報があるか、開発が活発か、というところに注目しておく必要があると感じています。 ** ECTを実行する 前述したような理由もあり、ECTについてはここでは簡単に紹介するだけに留めます。デモを用意しましたので、興味のある方は詳細を見てみてください。こちらも、Handlebarsと同様に、gulpとECTを利用して実際にHTMLを生成できるデモです。 //// ECTサンプルリポジトリ * https://github.com/codegrid/2015-build-html/tree/master/demo/ect https://github.com/codegrid/2015-build-html/tree/master/demo/ect //// ECTを使用するデモは以下のようなディレクトリ構成となっています。 pre.code | ect/ | ┣ package.json | ┣ gulpfile.js | ┣ src/ | ┃ ┣ page0.html # 各ページのHTMLとなるファイル | ┃ ┣ page1.html # 各ページのHTMLとなるファイル | ┃ ┣ page2.html # 各ページのHTMLとなるファイル | ┃ ┣ layout/ # 継承する雛形 | ┃ ┃ ┗ common.html # サイト共通の雛形 | ┃ ┗ include/ # インクルード | ┃ ┣ meta.hbs # meta要素など共通部分 | ┃ ┣ header.hbs # 共通ヘッダー | ┃ ┗ footer.hbs # 共通フッター | ┗ htdocs/ gulpからECTを扱うにはgulp-ect(#1)を使用しますが、2年以上更新されておらず、ファイルの読み込み部分で問題が起きるため、@hokaccha(#2)が修正したバージョンの https://github.com/hokaccha/gulp-ect を使用しています。 #1: https://github.com/avevlad/gulp-ect #2: https://github.com/hokaccha 実行方法は、Handlebarsと同様です。HTMLを生成するために必要なモジュールは、package.jsonに記述してあるので、demo/ect/ディレクトリに移動して、npm installを実行すれば必要なモジュールがインストールされます。そして、gulpfile.jsの中では、ECTのテンプレートファイルの場所や、出力されるディレクトリなどを指定しています。 demo/ect/ディレクトリでモジュールをインストールしたあとに、npm run buildと実行すると、srcディレクトリ以下のhtmlファイルを元にhtdocsディレクトリに、HTMLが生成されます。 ECTではHandlebarsと同様に、gulpfile.js内でデータを定義し、そのデータをテンプレート内で扱うことができます。また、ECTはテンプレート構文にCoffeeScriptのシンタックスが使えるという特徴があるため、CoffeeScriptがわかる方なら、すんなり受け入れられやすいかもしれません。 ――――――――――――――――― ここまでのまとめ ――――――――――――――――― gulpを使い、HandlebarsでHTMLを生成する方法とメリットとデメリットについて解説しました。また、ECTについても、簡単に紹介しました。どちらのテンプレートエンジンも、普段書いているHTML内にテンプレート構文を書く形になっています。 次回は、今回紹介したHandlebarsやECTとはまったく異なるテンプレート記法を持つJadeの紹介と、本連載のまとめをしていきましょう。 【 次号予告 】静的HTMLのためのテンプレートエンジン(3):Jade記法 ================= 【 技術情報 】 実践、jQuery(6):位置とサイズの取得 2 ================= offset()メソッドを利用し、html要素をオフセット要素とした相対的位置の取得、また位置の設定を解説します。シンプルな例だけでなく、疑似フレーム内に要素があった場合の挙動についても扱います。 (フロントエンド・エンジニア 森 大典) ◆アプリURL◆ https://app.codegrid.net/entry/jquery-position-2 ◆目次◆ - offset()メソッドによる位置の取得 - offset()メソッドによる位置の設定 - 要素を見切れさせないためには - offset()メソッドの位置補正を活用する - offset()メソッドの注意点 - まとめ 前回はposition()メソッドを解説しました。今回は引き続き、html要素からの相対的な位置を取得、設定できるoffset()メソッドを解説します。position()メソッドとの違いにも着目してみましょう。 なお、本記事は次の環境にて動作確認した内容になっています。 * jQuery 2.1.4 * OS X Yosemite 10.10.5 * Chrome 45.0.2454.101 * Firefox 39.0.8 * Safari 8.0.8 * Windows 7 * IE 11.0.9600.18015 記事内にて紹介するサンプルは次のリポジトリからダウンロード、またはクローンできます。併せて参照してください。 //// jQuery-Positionサンプルリポジトリ * codegrid/2015-jquery-position https://github.com/codegrid/2015-jquery-position //// ――――――――――――――――― offset()メソッドによる位置の取得 ――――――――――――――――― 前回解説したposition()メソッドと同様、offset()メソッドでも指定した要素の座標データを取得することができます。 pre.code.javascript | $(element).offset(); // {top:410, left:410 } position()メソッドとの違いはhtml要素をオフセット要素と解釈し、常にhtml要素からの相対位置を座標データとして返すということです。また、position()メソッドでは測定要素のmarginを加味した位置を返しましたが、offset()メソッドの場合は、測定要素そのものの位置を返します。 /* 図:offset()メソッドで要素の位置を取得する */ https://app.codegrid.net/s3/2014-practical-jquery/img/jquery-position/02/offset01.png 図が示すように、ブラウザのスクロール状態や、position: relativeなオフセット要素の存在の影響を受けず、常にhtml要素からの相対位置を返します。 /* デモ:offset()による位置取得 */ (ソースコード:demo_offset01.html(#1)) https://app.codegrid.net/s3/2014-practical-jquery/demo/jquery-position/02/demo_offset01.html #1: https://github.com/codegrid/2015-jquery-position/blob/gh-pages/02/demo_offset01.html ** 擬似フレーム内でのoffset()メソッドの使用 overflow: scrollを使用した擬似フレーム内にある要素に対しoffset()メソッドを使用した場合、物理的な配置位置からスクロール量分を差し引いた見た目上の座標を返します。これについてはposition()メソッドと同様の挙動ですが、原点位置が常にhtml要素であるという点が異なります。 /* 図:擬似フレーム内でのoffset()メソッドの使用 */ https://app.codegrid.net/s3/2014-practical-jquery/img/jquery-position/02/offset02.png 図が示すように、ブラウザのスクロール状態やオフセット要素の存在がhtml要素との見た目上の距離に変化をあたえることはありません。ただし、擬似フレームをスクロールした場合は、html要素との見た目上の距離が縮まりoffset()メソッドが返す座標にも変化が生じます。 /* デモ:offset()による位置取得(擬似フレーム内の要素に対し使用) */ (ソースコード:demo_offset02.html(#1)) https://app.codegrid.net/s3/2014-practical-jquery/demo/jquery-position/02/demo_offset02.html #1: https://github.com/codegrid/2015-jquery-position/blob/gh-pages/02/demo_offset02.html ** 測定要素がposition: fixedの場合 配置位置が固定されるposition: fixedの場合であっても、html要素からの見た目上の相対位置を返すという挙動は変わりません。 /* 図:offset()メソッドで擬似フレーム内要素の位置を取得 */ https://app.codegrid.net/s3/2014-practical-jquery/img/jquery-position/02/offsetFixed01.png position: fixedにより測定要素が位置固定された状態でブラウザをスクロールすると、原点位置であるhtml要素と測定要素の見た目上の距離は遠くなります。つまり測定要素がposition: fixedの場合のみ、ブラウザのスクロールの影響を受けた座標を返すということになります。 ちなみに前回解説したposition()メソッドをposition: fixedな要素に適用した場合は、ブラウザ表示領域の左上が原点位置となるため常に同一の座標を返します。 ――――――――――――――――― offset()メソッドによる位置の設定 ――――――――――――――――― offset()メソッドは要素の位置を取得するだけでなく、位置を設定することもできます。 要素を任意の位置に配置しようとする場合、css()メソッドでtopやleftを指定することが多いかと思います。 pre.code.javascript | $(element).css({ | top: 200 | }); この処理をposition: absoluteな複数の要素に対し適用した場合、すべての要素が同じtop位置に整列されることになります。ただし、先祖要素にオフセット要素を持っている場合、その要素が配置の原点位置になるため、見た目上、同じtop位置に整列されてないように見える可能性があります。 /* 図:css()メソッドによる位置揃え */ https://app.codegrid.net/s3/2014-practical-jquery/img/jquery-position/02/offset03.png offset()メソッドで要素の位置を設定した場合、こうしたオフセット要素の存在によって生じる見た目上のずれが補正されます。また、このときの座標の原点位置は常にhtml要素となります。 pre.code.javascript | $(element).offset({ | top: 200 | }); /* 図:offset()メソッドによる位置揃え */ https://app.codegrid.net/s3/2014-practical-jquery/img/jquery-position/02/offset04.png positionプロパティがstaticな要素に対し適用した場合は、強制的にrelativeに変更され配置位置が調整されます。 要素の表示位置を制御する上で基礎的なことですが、任意の座標に要素を表示しようとした場合、その要素のpositionプロパティをrelative、absolute、fixedのいずれかにし、その要素にとってのオフセット要素を原点位置としたtop、leftプロパティ(あるいはright、bottomプロパティ)で配置位置を調整する必要があります。 offset()メソッドでは、オフセット要素の存在や、positionプロパティの値によって異なる原点位置の解釈の違いを吸収し、見た目上、指定された位置に要素が表示されるよう物理座標の値を調整します。 次のデモではオフセット要素の異なる3つの要素に対し、positionプロパティをstaitc、relative、aboslute、fixedのいずれかに変更でき、css()メソッドとoffset()メソッドで配置位置を設定することができます。positionプロパティがどの値であってもoffset()メソッド実行後はすべての要素が整列して表示されることを確認してみてください。 /* デモ:offset()による位置設定 */ (ソースコード:demo_offset03.html(#1)) https://app.codegrid.net/s3/2014-practical-jquery/demo/jquery-position/02/demo_offset03.html #1: https://github.com/codegrid/2015-jquery-position/blob/gh-pages/02/demo_offset03.html ちなみにoffset()メソッドでは、次のように関数を引数にすることもできます。 pre.code.javascript | $(elements).offset(function(index, element){ | return {top:200}; | }); ** 擬似フレーム内の要素にoffset()で位置を設定する では擬似フレーム内の要素に対しoffset()メソッドで位置を設定したらどうなるでしょうか? 結果は次のとおり、擬似フレームのスクロールによって生じた見た目上のずれを、補正したかたちで配置されます。 /* 図:擬似フレーム内の要素にoffset()で位置を設定する */ https://app.codegrid.net/s3/2014-practical-jquery/img/jquery-position/02/offset05.png つまりoffset()メソッドによる位置設定は、「オフセット要素の有無」「positionプロパティの値」さらには「擬似フレームのスクロール状態」をも考慮し、見た目上、指定された位置に表示されるよう座標調整を行っているということになります。 /* デモ:offset()による位置設定(擬似フレーム内の要素に対し使用) */ (ソースコード:demo_offset04.html(#1)) https://app.codegrid.net/s3/2014-practical-jquery/demo/jquery-position/02/demo_offset04.html #1: https://github.com/codegrid/2015-jquery-position/blob/gh-pages/02/demo_offset04.html ――――――――――――――――― 要素を見切れさせないためには ――――――――――――――――― ここで前回同様、ドロップダウンメニューの配置処理の実装を例にoffset()メソッドの活用方法を考えてみたいと思います。 前回、position()メソッドによるドロップダウンメニューの配置処理を実装した際、position: absoluteなポップアップウィンドウ内でメニューを表示しようとすると、ボタンの位置に対しメニューの表示位置がずれるという問題が発生しました。 /* デモ:【再掲】オフセット要素の存在を考慮していない配置 */ (ソースコード:demo_dropdown02.html(#1)) https://app.codegrid.net/s3/2014-practical-jquery/demo/jquery-position/01/demo_dropdown02.html #1: https://github.com/codegrid/2015-jquery-position/blob/gh-pages/01/demo_dropdown02.html これに対し、ボタンがクリックされるつど、そのボタンにとってのオフセット要素内にメニュー要素を移動させ、ボタンとメニューの原点位置が一致してる状態を保つことで位置ずれを解消しました。 /* デモ:【再掲】オフセット要素の存在を考慮した配置 */ (ソースコード:demo_dropdown04.html(#1)) https://app.codegrid.net/s3/2014-practical-jquery/demo/jquery-position/01/demo_dropdown04.html #1: https://github.com/codegrid/2015-jquery-position/blob/gh-pages/01/demo_dropdown04.html では、ポップアップウィンドウのoverflowプロパティをscrollにし、スクロール可能な状態にした場合、メニューはどのように表示されるでしょうか? 次のデモを試してみてください。 /* デモ:ポップアップウィンドウがスクロール可能な状態 */ (ソースコード:demo_offset_dropdown01.html(#1)) https://app.codegrid.net/s3/2014-practical-jquery/demo/jquery-position/02/demo_offset_dropdown01.html #1: https://github.com/codegrid/2015-jquery-position/blob/gh-pages/02/demo_offset_dropdown01.html メニューがポップアップウィンドウの外側にはみ出すことができず、見切れて表示されたのではないでしょうか? overflowプロパティにscrollやhiddenが指定された場合、表示枠内に収まらない部分は、このように見切れて表示されてしまいます。 メニュー全体を表示するには次の2つの方法が考えられます。 * 見切れない位置にメニュー要素をずらして表示する * メニュー要素をポップアップウィンドウの外側に配置したままにし、原点位置のずれ分を位置補正して表示する ここでは後者の方法について考えてみたいと思います。サンプルで使用しているようなシンプルな構成であればさほど難しくない処理ですが、どのような状況でも利用に耐えられる汎用性の高い実装にするには、どうすれば良いでしょうか? ――――――――――――――――― offset()メソッドの位置補正を活用する ――――――――――――――――― このような場合、offset()メソッドで位置の取得と設定を行うと簡単に解決することができます。offset()メソッドは「html要素を原点位置に見た目上の要素位置の取得、設定ができる」というシンプルなルールで動作するため、原点位置の差分補正といった面倒なことを考える必要がありません。 具体的には次のような実装になります。 pre.code.html | <a class="menuBtn">click</a> | <a class="menuBtn">click</a> | <a class="menuBtn">click</a> | | <!-- ポップアップウィンドウ--> | <div class="popup"> | <a class="menuBtn">click</a> | <a class="menuBtn">click</a> | <a class="menuBtn">click</a> | </div> | | <div class="menu"> | <ul> | <li>menu1</li> | <li>menu2</li> | <li>menu3</li> | </ul> | </div> pre.code.javascript | // メニュー表示処理 | $.fn.showMenu = function(){ | | // メニューのjQueryオブジェクトを取得 | var $menu = $('.menu'); | | // ボタンのjQueryオブジェクトを取得 | var $button = $(this); | | // ボタンの配置位置を取得 | var buttonOffset = $button.offset(); | | | // メニューの配置位置を算出 | var menuPos = { | | // ボタンのY座標(top) + ボタンの高さ(height) | top: buttonOffset.top + $button.height(), | | // ボタンのY座標(left) | left: buttonOffset.left | }; | | // メニューを表示し配置位置を設定 | $menu.show().offset(menuPos); | | }; | | // ボタンクリック時処理 | $('.menuBtn').on('click', function(){ | | // メニュー表示処理 | $(this).showMenu(); | }); 比較するとわかりますが、前回、position()メソッドで実装した際の汎用性を高める前のコードを、offset()メソッドに置き換えたものと、ほぼ同じ内容になっています。位置ずれが発生しないことを次のデモで確認してみてください。 /* デモ:offset()メソッドの位置補正を活用 */ (ソースコード:demo_offset_dropdown01.html(#1)) https://app.codegrid.net/s3/2014-practical-jquery/demo/jquery-position/02/demo_offset_dropdown02.html #1: https://github.com/codegrid/2015-jquery-position/blob/gh-pages/02/demo_offset_dropdown02.html //// コラム:異なるオフセット要素を持つ要素の分離 前掲のデモにてドロップダウンメニューが表示された状態で、ポップアップウィンドウをスクロールさせると次のようにボタンとメニューが切り離されたかたちで表示されてしまいます。 /* 図:ボタンとメニューが分かれてしまう */ https://app.codegrid.net/s3/2014-practical-jquery/img/jquery-position/02/offset_gap01.png このように異なるオフセット要素を持つ要素同士を繋いだ一体感のあるデザインを採用する場合、ウィンドウのリサイズやスクロール状態によって要素が分離しないか、あるいは分離しても問題がないデザインかなどをよく検討したほうがよいでしょう。 //// ――――――――――――――――― offset()メソッドの注意点 ――――――――――――――――― 非常に便利なoffset()メソッドですが、一点気を付けなければいけないことがあります。それは座標の取得、設定を対象要素が表示された状態で行う必要があるということです。前掲のソースコードでは次の箇所が該当します。 pre.code.javascript | // メニューを表示し配置位置を設定 | $menu.show().offset(menuPos); 要素を表示してから座標を設定していることがわかります。position()メソッドをはじめ、対象となる要素が表示された状態でないと、正しい位置やサイズが取得できないメソッドは他にもあります。が、offset()メソッドの場合、値の設定においても表示状態である必要があるので、注意して使用したいところです。 /* デモ:offset()とshow()の実行順による結果の違い */ (ソースコード:demo_offset_timing01.html(#1)) https://app.codegrid.net/s3/2014-practical-jquery/demo/jquery-position/02/demo_offset_timing01.html #1: https://github.com/codegrid/2015-jquery-position/blob/gh-pages/02/demo_offset_timing01.html ――――――――――――――――― まとめ ――――――――――――――――― 今回はoffset()メソッドによる、要素位置の取得と、設定について解説しました。前回解説したposition()メソッドとの位置測定の基準点の違いなども含め、整理しておくとよいでしょう。 次回は、スクロール位置の取得、設定、イベント発生位置の取得などについて解説します。 【 次号予告 】実践、jQuery(7):位置とサイズの取得 3 ================= 編集後記 ================= めっきり寒くなってきましたね。自宅の仕事場では足温器を愛用しているのですが、飼い犬(室内犬)と足温器をめぐって静かなバトルが足元で繰り広げられる季節になりました。やっぱり、犬も暖かいのがよいらしいです。(まる) コードを書いているときの目からウロコ体験ってありますか? 私は、コメントアウト/コメントアウト解除が簡単にできるのを知ったときでした。cmd+/で。試行錯誤が楽にできると知った、あの瞬間。(soto) ================= ご意見、ご要望、ご質問などのお問い合わせ ================= CodeGrid フロントエンドに関わる人々のガイド https://www.codegrid.net/ このメールマガジンへのご意見・ご感想・ご質問は、このメールに返信いただくか、こちらのお問い合わせまで。 https://www.codegrid.net/contact.html codegrid@pxgrid.com 配信先のメールアドレスの変更・退会は、こちらでお手続きください。(ログインが必要です) https://app.codegrid.net/setting Twitter(@CodeGrid) https://twitter.com/CodeGrid 著作・発行:株式会社ピクセルグリッド https://www.pxgrid.com/ ━━━━━━━━━━━━━━━━━ 本メールの無断全文転載はご遠慮ください。