canvasでマスクをしてみる

HTMLの装飾に関する話題です。
HTML5で追加される要素の一つにcanvasというものがあります。
canvasは、java scriptでベクター画像を描画するものなのですが、これを利用して画像にマスク処理をつけることができます。
こんな感じ。

DEMO

$(window).load(function() {
    $('#canvasList img').each(function() {
      createCanvas(this);

    //canvasに未対応の場合は、canvas要素の削除 対応している場合はimg要素の削除
    if($.browser.msie && $.browser.version < 9){
        $('canvas').remove();
    }else {
        $(this).remove();
    }
  });
});
function createCanvas(images) {
    var canvas = document.createElement('canvas');
    if(canvas.getContext){
        var ctx = canvas.getContext('2d');

    //canvasに画像の幅、高さを代入
    canvas.width = images.width;
    canvas.height = images.height;

    //マスクとなるcanvasを描画
    ctx.beginPath();
    ctx.moveTo(318.0, 119.2);
    ctx.bezierCurveTo(318.1, 230.7, 318.1, 237.7, 159.0, 237.7);
    ctx.bezierCurveTo(9.9, 237.7, 0.0, 184.6, 0.0, 119.2);
    ctx.bezierCurveTo(0.0, 53.8, 0.2, 8.3, 159.0, 0.7);
    ctx.bezierCurveTo(327.0, -7.3, 318.0, 53.8, 318.0, 119.2);
    ctx.closePath();

    //canvasに画像を貼り付け
    ctx.clip();
    ctx.drawImage(images, 0, 0);
    }
  images.parentNode.insertBefore(canvas, images);
};
&#91;/javascript&#93;

&#91;html&#93;
<div id="canvasList">
  <img src="images/img01.jpg" class="imgSrc" />
  <img src="images/img02.jpg" class="imgSrc2" />
</div>	
[/html]

canvasについて、深く語れる程の知識は持ち合わせていないのですが・・・
ざっくり基礎的な事をご説明すると

[javascript]
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');

この2行がcanvasのAPIを扱うときの決まり文句で、まず、canvas要素を取得して、.getContext()メッソドを定義する事で、canvasで描画するためのメソッドやプロパティが取り出せるようになります。
ちなみに.getContext()の引数は、2dしか定義されていません。3dと定義して立体映像を描くなんてことはできないです。

ctx.beginPath();
ctx.moveTo(318.0, 119.2);
ctx.bezierCurveTo(318.1, 230.7, 318.1, 237.7, 159.0, 237.7);
ctx.bezierCurveTo(9.9, 237.7, 0.0, 184.6, 0.0, 119.2);
ctx.bezierCurveTo(0.0, 53.8, 0.2, 8.3, 159.0, 0.7);
ctx.bezierCurveTo(327.0, -7.3, 318.0, 53.8, 318.0, 119.2);
ctx.closePath();

こんな感じでで、座業を定義していきます。
ctx.beginPath();でパスの開始を定義して、ctx.closePath();で終わりを定義する感じですね。

この座標情報ですが、もちろん手書きで定義なんてできません。
Ai2Canvasというイラストレーターのプラグインを利用すると、簡単に書き出せます。

ctx.clip();
ctx.drawImage(images, 0, 0);

これでcanvasに画像を貼付けます。


$(window).load(function() {
$(‘#canvasList img’).each(function() {
createCanvas(this);

//canvasに未対応の場合は、canvas要素の削除 対応している場合はimg要素の削除
if($.browser.msie && $.browser.version < 9){ $('canvas').remove(); }else { $(this).remove(); } }); }); [/javascript] 最後に、#canvasList内の画像に対して、関数化してあったcanvasでのマスク処理を適用させます。 今回僕が作ったサンプルですと、元の画像をcanvasに貼付ける形になっていますので、このままですと、元の画像とcanvasで描画したマスク処理の画像の二つが表示されてしまいます。 それだと困りますので、画像を削除するわけですが、ここで、canvas未対応のブラウザを判別して、未対応の場合は、canvas要素を削除、それ以外は元の画像を削除するように指定する事で、どのブラウザでもそれなりに表示されるようにしてあります。 このような手法のメリットとして、元の画像はjpgでよいので、透過pngにする必要がなく、ファイルサイズを圧縮できる点があげられます。今回のサンプルですと、元画像をjpgにしてマスク処理をする場合、画像サイズは、33kbだったのに対して、同じような切り抜きをしたpng画像にすると124kbとなっていました。 元画像
透過pngにした場合

また、コードを書くだけで多量の画像にマスク処理を施せますので、作業効率もよいです。マスク自体もcanvasで描画しているので、動くマスク表現なんかのできてしまいます。そう、もはやFlashを使わなくてもかなりのデザイン表現ができるのです!

画像が大きかったり、マスクが複雑だと処理に時間がかかって、初回のロード時に一瞬元の画像が表示されて後からマスクがかかるような場合もあるかもしれません。
実務として利用するには、もう少し改良が必要ですが、一手間かけるだけで、表現力が増すのでは、こういった手法も覚えておくといいかもしれませんね。

【jQuery】子要素が空の時の判定

あまり使用用途がないかもしれませんが、ちょっとはまってしまったのでメモとして。

:not(:has(子要素))
で、子要素がない時というセレクターになります。
で、これを使って、子要素にアイテムがない場合、そのブロックを消してしまう。なんてことができないかと思案しておりまして・・・

html

<div class="block">
     <h1>タイトル</h1>
     <ul>
          <li>リスト</li>
          <li>リスト</li>
          <li>リスト</li>
     </ul>
</div>

こんなソースがあった場合に、リストの有無を判別して、空っぽだったら親要素ごと消してしまいたいのです。

実験その1

if文の判定に、セレクターをいれてしまう。
どうもうまくいきません。li要素があろうとなかろうと消えてしまいますので、おそらくif文の判定がうまく通っていないんでしょう。
DEMO

$('div.block').each(function(){
  if( $(this).children('ul:not(:has(li))') ){
    $(this).remove()
  }
})

実験その2

うまく通るようになりました。

DEMO

$('ul.hoge:not(:has(li))').addClass('removeThis');
$('div.block').each(function(){
      if( $(this).children('ul').hasClass('removeThis') ){
         $(this).remove();
     }
})

まず、先述の判定で、子要素のliが無い時、ulにremoveThisというクラスを付与します。
次に、親ブロックを検索して、removeThisというクラスが付いたulがあった場合は、そのブロックを削除すると。
ちょっと回りくどいですが、親ブロックが複数ある場合を想定するとこのような書き方になります。

も少しスマートにできるといいんですけどね。

jqueryでブログのフィードを表示する(IEも見れるよ)

あまり一般的でないネタだと思いますが、ここ数日大変はまったので情報をシェアします。
ブログなのどのRSS情報を読み込む時に、rssフィードなどを読み込んで表示させます。

その方法については、僕も記事にしたことがあったのですが、
[js]callback関数を使ってみた

今回はjGfeed.jsでは対応できずに、jQueryの.ajaxを使って処理を行わないといけない案件でした。

なにがしたかったかというと、rss.xmlを読み込んで記事のタイトルと、記事をまるまる(htmlソースも含め)再現したかったのです。

こんな感じ>>DEMO

前述のjGfeedですと、読み込める要素がGoogle Feed APIに依存してしまうというデメリットがあります。
タイトルなどは拾えますが、記事を完全に再現することはできないのです。

[追記]
うそのようです・・・Google Feed APIでもhtmlタグ付きのエントリーを拾えそうです。えっと、ここまで書いてあれなので、その辺の検証は後日しようと思います。ごめんなさい。
[追記ここまで]

そうなると、.ajaxなどでxmlをパースする必要がでてきますので、xmlを展開して、html要素を生成しなければなりません。

で、ここまではそれほど問題がないのですが、今回のキモとなるのが “content:encoded”という要素。ここに取り出したいタグ付きの記事本文が入っているんですけど、なかなか拾えないんです・・・特にIEでorz

ちなみに、rss.xmlはこんな感じです。一つの記事のものを抜粋しましたが、中段付近にあるのが今回拾いたいcontent:encoded要素です。

<item>
		<title>『BE ソーシャル!』出版記念斉藤徹氏講演会 in 長野</title>
		<link>http://meusonho41.com/blog/20121003_besocia/</link>
		<comments>http://meusonho41.com/blog/20121003_besocia/#comments</comments>
		<pubDate>Wed, 03 Oct 2012 13:33:51 +0000</pubDate>
		<dc:creator>4_1</dc:creator>
				<category><!&#91;CDATA&#91;イベント&#93;&#93;></category>

		<guid isPermaLink="false">http://meusonho41.com/blog/?p=187</guid>
		<description><!&#91;CDATA&#91;長野ソーシャルシフトの会が主催で、斉藤さんの講演会が決定しました。 ただただ楽しみで仕方ありません。 もちろん...  <a href="http://meusonho41.com/blog/20121003_besocia/" title="Read 『BE ソーシャル!』出版記念斉藤徹氏講演会 in 長野">Read more &#187;</a>&#93;&#93;></description>
			<content:encoded><!&#91;CDATA&#91;<p>長野ソーシャルシフトの会が主催で、斉藤さんの講演会が決定しました。<br />
ただただ楽しみで仕方ありません。<br />
もちろん、僕も参加させて頂きます。(もしかしたら裏方になるかもしれませんが)</p>
<p>参加申し込みは本日より先着35名となっておりますので、どうぞお早めに。</p>
<h4 class="subTitle"><span>開催概要</span></h4>
<p>●日時<br />
2012年12月7日(金) <br />
開場19:00<br />
開演 19:15<br />
終了 21:15<br />
※時間配分は予定です。</p>
<p>●会場<br />
長野市 生涯学習センター (TOiGO) 第1学習室<br />
〒380-0834 長野市大字鶴賀問御所町1271-3 TOiGO WEST 3F<br />
Tel:026-233-8080 / Fax:026-233-8081<br />
<a href="http://www.toigo.co.jp/access/">http://www.toigo.co.jp/access/</a></p>
<p>●参加費、定員<br />
2,000 円(35名:先着順)</p>
<p>●チラシPDFのダウンロード<br />
<a href="https://docs.google.com/open?id=0B_irC-xm0Wf2N2FiLTNDZ3d6SDQ">https://docs.google.com/open?id=0B_irC-xm0Wf2N2FiLTNDZ3d6SDQ</a></p>
<p>●申し込みフォーム<br />
<a href="http://kokucheese.com/event/index/55651/">http://kokucheese.com/event/index/55651/</a></p>
<p>詳しい情報などは長野ソーシャルシフトの会FaceBookページをご覧ください。<br />
<a href="http://www.facebook.com/naganosocialshift">http://www.facebook.com/naganosocialshift</a></p>
&#93;&#93;></content:encoded>
			<wfw:commentRss>http://meusonho41.com/blog/20121003_besocia/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>

IE以外の環境であれば、.find(“encoded”)と書けば拾えるんですけど、どうも協調性がない子がいまして。
退避策は、ググると色々でてきます。問題となるのが「:」なのでそれをエスケープする方法として、
.find(‘[nodeName=”content:encoded”]’).text()
とか
.find(‘content\\:encoded’)
とか

どれもできませんでした(涙)
この時点で、なんどIEを爆破してやろうと考えたことか・・・もちろんそんなことできませんけど。

で、たまたま見つけた方法がこれ

$(xml).find("item").children().each(function() {
   if ($(this)[0].tagName == "content:encoded") {
      contentItem = $(this).text();
    }
});

xmlを読み込んで、itemタグの子要素をぐるぐる回して、そのなかに “content:encoded”というタグがあったら、その中身をcontentItemという変数に代入すると。大変まどろっこしい方法ですが、これで、IE7以上でも表示されるようになりました。(ネイティブなIE6環境が手元になかったので確認できていないだけです)

.ajaxでXMLを読み込む場合、クロスドメインの問題などもありますのであまり実用性はありませんが、blog+スタティックなhtmlでのサイト構築時など同一ドメイン内であれば利用も可能ですし、今後xmlに限らす、jsonとかも含め、動的にhtml要素を生成する機会も増えてくると思うので、こういったテクニックは思えておきたいですね。

いや、しっかし、これは本当にはまった・・・

googlemap 同じ地図を複数表示させる

当たり前と言えば、当たり前なのですが、少しはまって解決したので。

googlemap をサイトに埋め込む状況は沢山ありますが、たとえば、サイドバーに小さい地図をいれて、
会社案内のページに大きな地図をいれるようなことがあると思います。
そんな時

jQuery(document).ready(function($) {
  var latlng = new google.maps.LatLng(36.238701,137.969526);
        var options = {
            zoom: 15,
            center: latlng,
            navigationControl: false,
            mapTypeId: google.maps.MapTypeId.ROADMAP
        };
        var map = new google.maps.Map(document.getElementById('sdMap'), options);
        var sdMap = new google.maps.Map(document.getElementById('map'), options);
       
        var marker = new google.maps.Marker({
            position: new google.maps.LatLng(36.238701,137.969526),
            map: map
    });

        var marker2 = new google.maps.Marker({
            position: new google.maps.LatLng(36.238701,137.969526),
            map: sdMap
    });

});

こんな風にvar map・・・を複数記述すると、複数の地図を描画してくれるんですね。
ただし、マーカーを付ける場合、地図を表示させる変数を指定しないといけないので、
その部分は重複して記述しないとだめのようです。

あと、jQueryの$(document).readyでいけるんですね。

知らなかった・・・

DEMO

えっと、それだけじゃあれなんで、googlemapのネタをもう一つ。

上記のような方法でgooglemapを埋め込む時、座標を調べなければなりません。
いくつか専用サービスがありますが、google mapを使うと便利です。

google mapで目的の場所を表示して、地図上で「右クリック」→「この場所について」で、座標を表示してくれます。

地味に便利なので覚えておくといいかもしれません。

Slippyでプレゼン資料を作ってみた

仕事で、簡単なプレゼンをすることになりましたので、せっかくだから、HTMLベースのプレゼンスライドをSlippyというライブラリで作ってみました。

結構楽チンでスライドができましたよ。

基本構造は簡単で、

<div class="slide">
<p>ここにスライドの中身を書きます</p>
</div>

こんな感じで、.slideで囲んであげるとそれが、1枚のスライドとして認識してくれます。
他にも、要素に.incrementalと付けると、非表示で表示アクションをすると表示されるようになります。

<p class="incremental">非表示から表示するよ<p>

DEMO

そして、何より、CSSで装飾ができるのがいいですね。
プレゼンソフトを使い慣れていない身としては、まず扱うという所から始めないといけません。
その点、こういったHTMLベースのスライドの場合、普段から使い慣れているので、ある程度自由にデザインできます。
資料の配布も、webにアップするだけでいいので、扱いやすいですよね。

もちろん、制約というかもあって、立体的にスライドをうごかしたり、アップルのキーノートのようすごい動きとかは無理です。どちらかというと、テキスト中心のスライドに向いているかもしれません。

これから勉強会とか頻度よく行なっていこうと思っているので、テンポよくスライドが作れるツールとして重宝しそうです。

えっと、今回作ったプレゼン資料は、気が向いたら公開しますが、まだ準備段階なので・・・

java script配列のエラー

ちょっとハマったので備忘録として。

java scriptで配列を扱う時こんな感じで書きます。

var hoge = [{
  permalink: http://meusonho41.com,
  date: 2012/09/08,
  title: my web site
}];

配列の要素を「,」で続けて記述しますが、この時に最後の要素の後ろに「,」を付けてしまうと
IE7以下(IE8互換モードも)でエラーになってしまいます。

var hoge = [{
  permalink: http://meusonho41.com,
  date: 2012/09/08,
  title: my web site,
}];

『識別子、文字列または数がありません。』

エラーの文言から推測するに、「,」があることで、続きがあると認識してしまうからでしょうか。
真偽の程はわかりませんが、この「,」を取るとエラーは無くなりましたので、きっとそうゆうことでしょう。

IE7以下となると、もはやニッチな・・・とも言えませんので、きちんと対応できるように覚えておきます。

しかし、IE9の開発モード便利ですね。過去のバージョンに遡って表示してくれます。(ブラウザモード変更)
(きちんとした検証にはネイティブ環境の方がいいですけど)
へんなテスターよりよっぽどいい。たまにはやるじゃん、IE君。

ちなみに、このブラウザモード googleさんも騙されてたw やるじゃん、IE君。

java scriptでトランケート処理をする

かなりニッチなネタなんですが、よく忘れてしまうので備忘録として。

java scriptでRSSなどを動的に読み込んだ際のトランケート処理です。
仰々しく書きましたが、単純な処理なんですけどね。

var title =  $(this).find("title").first().text();

if(title.length>8){
  title = title.substring(0,8)+'…';
} else {
  title = $(this).find("title").first().text();
}

動的なアイテムを変数に入れて、.substring()で必要な文字だけ切り出すと。
ついでに、文字数を見て、特定の文字数(今回は8字)よりも大きい時だけ処理するようにif文を入れてあります。

えっと、そんな訳で完全に個人の備忘録メモでした。

たった2行で実現できるユーザービリティの高いフォームの作り方

人様が作ったライブラリなのですが、とてもとても便利なのでシェアさせて頂きたいと思います。

Kawa.netxp AjaxZip 2.0

お問い合わせフォームなどで住所を入力する欄で使える入力アシスタント機能が文字通りたったの2行で実現できるライブラリです。詳しくは、配布元サイトをご覧頂ければいいかと思いますが、基本的な構造はこんな感じ。

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<script type="text/javascript" src="jquery.zip2addr.js"></script>

<script type="text/javascript">
$(function(){
     $('#zip').zip2addr('#addr')
});
</script>

<dl>
  <dt>郵便番号</dt>
    <dd><input type="text" name="" id="zip" size="8"/></dd>
  <dt>住所</dt>
    <dd>
      <input type="text" name="" id="addr" size="34" />
  </dd>
</dl>

DEMO

送信前のhtmlに対してjqueryを使って住所を補完していますので、java scriptが使える環境であれば、だいたいどこでも実装可能かと思います。(実例として、僕が使っているCMSのお問い合わせフォームでも実装できました。)

今回のデモの他に、配布元のサンプルには、都道府県を分けるパワーンとかいくつか掲載してますので、用途に合わせて使えるんじゃないでしょうか。

この手の機能は、あればあるに越したことがないと思いますが、実装が手間だったりしてどうしても見て見ぬふりをしがちな部分です。これだけ簡単に実装できるのであれば、使わない手はないかと。

住所に限らず、フォームは入力の手間をできるだけ省いて、ユーザービリティをきちんと考えて実装できるようにしたいですね。

jQuery templeateでhtmlとjava scriptを分離する

先日書いたの記事の続きというか、もう少しだけ解説を。

jsonファイルから動的にリストを生成する部分です。
これは、jquery templateというライブラリを使ってリストを作っています。
このライブラリを使うと、java scriptのコードとhtmlのコードを分離して扱うことができるようになります。

ライブラリを読み込んで、下記のようなコードで実現可能です。

<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
//jsonを読み込む。ajaxがイミフなのでコード内に書きましたけどなにか?
var jsonAray = list;
var spotList = $( '#spotListTmpl').tmpl( jsonAray );
$('#spotList').append( spotList );
$('#spotList').listview('refresh');
<!-- ここに生成されたhtml要素が挿入されます -->
<ul data-role="listview" data-inset="true" id="spotList">
</ul>

<!-- htmlの構造を定義 -->
<script id="spotListTmpl" type="text/x-jquery-tmpl">
<li><a href="${url}">${title}</a></li>
</script>

直感的でわかりやすいんじゃないかと思うのですが、どうでしょうか。

この部分で、htmlタグと動的に読み込む要素(jsonとかxmlとか)を使って構造を作って
指定した場所に放り込んであげるわけですね。
java scriptでhtml要素を生成する際、改行が出来なかったりする関係でどうしても可読性が落ちてしまいがちです。
さらに変数とかを入れたりすると悲惨ですよね。

"<li><a href='" + url + "'>" + title + "</a></li>"

通常の書き方だとこんな感じ?この程度ならなんとかなりますが、もう少し複雑になるとお手上げですorz

jquery templateは、jsonとか、xmlを扱える方であれば、比較的簡単に導入できるテクニックじゃないかと思います。

ちなみに、本来はappendするコードを放り込ませる要素よりも下に書かなければなりません。
が、どうもうまく動いちゃってますね。なんでだろ?

最後に重要なこと。
jquery mobileでは、最初の読み込み時にhtml要素に対してスタイルを適用させます。
つまり、後から読み込んだ要素にはjquery mobileのスタイルが適用されません。
動的に生成したli要素に対しては、

$('#spotList').listview('refresh');

という様に、再読み込みをさせてスタイルを提要させる必要があります。

jquety mobileとjquery template2つのライブラリを使ったデモでしたので、ちょっと分かりにくくなってしまいましたがどちらも扱えるようになると表現の幅が広がると思うのでマスターしたいですね。
・・・jquety mobileはちょっと扱いにくいですが。

jQuery mobileのイベントについて考えてみた

昨日公開した記事のgooglemapですが、どうも最初の読み込み時にうまく表示されていません。
リロードすれば表示されるようになったりするのですが、気持ち悪いです。

あまりに気持ち悪いので原因を調べてみました。

解決法:googlemapを表示するコードを実行するイベントをpageshow()に変える

DEMO

$(document).ready(function(){
   $('#map').bind('pageshow',function(){
      var latlng = new google.maps.LatLng(36.238874,137.968613);
      var options = {
         zoom: 15,
         center: latlng,
         mapTypeId: google.maps.MapTypeId.ROADMAP
      };
      var map = new google.maps.Map(document.getElementById('map_canvas'), options);
      var pageHeight = $(document).height();
      $('#map_canvas').css('height',pageHeight);
   });
});

たぶん、実行のタイミングに問題があるんだと思います。
初期化系のイベントで試したところ、どれも同じような感じでした。
それに対して、pageshowだとうまくいくんです。

DEMO(地図の表示テストをご覧ください)

実際の実行順でいうと
pagebeforecreate→pagecreate→pageinit→pageshowとなっています。

DEMO(イベントの実行順をご確認ください)

ちなみに、pageshowは、ページ遷移のアニメーションの完了後(=ページが完全に表示されたら)実行されます。

googlemapはコードの一番下に書いた方がいいよみたいなおまじないがありましたが、それはおそらくページの読み込みが完全に終わってから実行しないとうまく表示できないからだと思いますが、今回の例も同様で、pageshow=ページが完全に表示されてから実行しないとだめということかな。

なんとも歯切れの悪い説明で申し訳ありませんが、ノウハウとしてjquery mobileでgoogle mapを扱う際には、pageshowで実行すると覚えておいたほうがいいかもしれませんね。