with the flow

WEBプログラマを目指すWEBデザイナーが書き綴る開発日誌のようなもの

Canvasでjpgを透過PNGに変換するjQueryプラグイン作った

背景を透けて見せる等ができる透過PNGを、canvasで作成できる(グラデーションアルファマスクを作る)jQueryプラグイン作った。

f:id:mhstid:20131105125224j:plain

透過PNGRGB+Alphaチャンネルなので、今回みたいに写真の透過PNGだと
普通にサイズが1M近くになっちゃう。でもデザインの都合上透過PNGでなきゃいけない場合もあるわけで。。。

そこで、JPG画像とマスク用の画像を元にして
canvasで透過PNGに加工してしまい、サイズを節約するプラギン作りました。
要はPhotoShopで普段やる「透過PNG書き出し」をcanvasでやってしまおうというもの。

→これでサイズが1/5位にできた!調べたらAppleのサイトとかでもフツーに使われてるテクニックだった。
ということでおそらくもっと優秀なプラギンは山のようにあると思います。

あと一応、画像がそれぞれ呼ばれた後と、すべての画像加工が終わった後にcallbackできるようにしています。
今回使ってないけど。詳しくはソースを。

苦労したのはマスクを掛けるjpeg画像とマスク元画像が完全にloadされてからcanvasでdrawしないと
どちらかしか・またはまったく描画されないこと。
特にIEではキャッシュ画像を読んだ場合はonload自体が発生しないことがある。IEまたおまえか。。。
ということで、BuggyなIE9の場合は、画像を呼び出すときに末尾にクエリストリングをつけるという苦肉の策で対応。

使い方。

■HTML側
→透過PNG生成に必要な画像のパスは、すべてhtml側にdata属性で書き出しておく。
srcに写真のURL書いたらリクエストが飛んじゃって、読み込みサイズを小さくするという
プラグインの目的が果たせなくなるので、透過gif画像にしています。

※どうしてもSEO的にやりたくない場合はdisplay: noneか、こういうテクニックを利用して見えなくしたaタグにimgへのリンク貼るとかしか思いつかないです。

<img src="blank.gif"  data-origsrc="original.jpg" data-jpegsrc="image.jpg" data-filtersrc="filter.png" width="200" height="200" alt="image alt">

"data-origsrc"にはcanvas非対応ブラウザ用に透過PNG版の画像パス、
"data-jpegsrc"にはアルファマスクしたいjpeg画像(png,gifでも可)、
"data-filtersrc"にはアルファマスクをするpng画像(透明にしたい部分を黒にした透過png)を指定。

■JS側
onready以降の好きなタイミングでcreateAlphaJpegを実行するだけ。

$(function() {
  $("img").createAlphaJpeg();
  $("#imgWrapper").createAlphaJpeg();//(←画像を含むwrapperでも可能)
});

optionsも指定できますが詳しくはソースを見てください。
以下のURLを参考にしたけど、まだバグがあるかも。。。

参考にしたプラグインスニペット
https://gist.github.com/yemster/1539102
https://github.com/7vsy/ImgLoader/blob/master/ImgLoader.js

一応作ったJSも貼っておきます。

(function($, undefined) {
	$.fn.createAlphaJpeg = function(options) {
		var $this = this,
			$images = $this.find('img').add($this.filter('img')),//thisがimg自体でもimgを含むdivなどでも、すべて検索してとにかくimgを拾ってくる
			len = $images.length,
			blankGif = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==',
			options = $.extend({
				afterEachImageDrawed: function(){  },
				afterAllImagesDrawed: function(){  },
				origSrc: "origsrc",
				filterSrc: "filtersrc",
				jpegSrc: "jpegsrc",
				bugBrowser: ['msie 9'],
				alphaJpegClass : 'alphaJpeg'
			}, options),
			canUseCachedImages = (function() {// IE9がキャッシュ画像を使ったときにloadが発生しないことがある為対策
				var ua = window.navigator.userAgent.toLowerCase();
				var m = ua.match(/msie ([0-9]*)/);
                if (m != null && $.inArray(m[0], options.bugBrowser) !== -1) {
		    		return false;
			    }else{
			    	return true;
			    }
			})(),
			isCanvasSupported = (function() {
				var elem = document.createElement('canvas');
				return !!(elem.getContext && elem.getContext('2d'));
			})(),
			$currentImage = [],
			filterImg = [],
			jpegImage = [],
			$canvasImage = [],
			drawedImagesCnt = 0;

		// functions
		function loadFilter(id) {
			filterImg[id] = new Image();
			filterImg[id].onload = function(){ createJpeg(id) };
			var src = $currentImage[id].attr("data-" + options.filterSrc);
			src = (canUseCachedImages)? src : src + "?" + new Date().getTime();
			filterImg[id].src = blankGif;
			filterImg[id].src = src;
		}
		function createJpeg(id) {
			jpegImage[id] = new Image();
			jpegImage[id].onload = function(){ createCanvas(id) };
			var src = $currentImage[id].attr("data-" + options.jpegSrc);
			src = (canUseCachedImages)? src : src + "?" + new Date().getTime();
			jpegImage[id].src = blankGif;//webkit hack from https://groups.google.com/forum/#!topic/jquery-dev/7uarey2lDh8
			jpegImage[id].src = src;
		}
		function createCanvas(id) {
			var w = $currentImage[id].width();
			var h = $currentImage[id].height();
			$canvasImage[id] = $('<canvas class="' + options.alphaJpegClass + '" width="' + w + '" height="' + h + '"></canvas>');
			$currentImage[id].replaceWith($canvasImage[id]);
			var ctx = $canvasImage[id][0].getContext('2d');
			ctx.drawImage(jpegImage[id], 0, 0, w, h);
			ctx.globalCompositeOperation = 'xor';//重なり部分を除外モードにする
			ctx.drawImage(filterImg[id], 0, 0, w, h);
			drawedImagesCnt++;
			options.afterEachImageDrawed.call($this, $canvasImage);
			if (drawedImagesCnt === len) options.afterAllImagesDrawed.call($this, $canvasImage);
		}
		for (var i = 0; i < len; i++) {
			$currentImage[i] = $images.eq(i);
    		if ($currentImage[i].attr("data-" + options.origSrc) == null) break;
			if (!isCanvasSupported) {//canvas非サポートの場合はJPEGをそのまま書き出す
				$currentImage[i].attr("src", $currentImage[i].attr("data-" + options.origSrc));
			} else {
				loadFilter(i);
			}
		}
		return $this;
	};
})(jQuery);

$(function()
{
	$("img").createAlphaJpeg();
});

※MITなので使うのは自由ですが当方で責任は一切持ちません。