Grayscale Image Gallery with filter effects

Category : CSS, jQuery, SVG

以前、元画像を変更せずに画像をグレースケールで表示する という記事で「canvas での変換は重いから現実的じゃない」みたいなことを書いたのですが、-webkit-filter が使えるようになった(なる)ことにより、もう少し負担を減らして使えるのではないかと思いました。
そこで、なるべく canvas での変換を行わずにやってみようと思います。

今回作ったものはこれです。

Firefox をご利用の方へ。

環境によっては、CSS グラデーションを background-size で小さくして background-repeat で敷き詰めると、gfx.direct2d.disabled が false の場合にフリーズするようです。(Firefox 10.0.2 で確認)

Firefox で見ても安全なデモページは こちら です。
本来のデモページから小さな点々を除いたものになりまうす。

Grayscale Image Gallery

デモファイルをダウンロード

使うものは、HTML, CSS, SVG, JavaScript です。

まずは HTML から。

<ul class="gallery">
  <li>
    <a href="#">
      <span><img src="images/01.jpg" width="200" height="200" alt="" /></span>
    </a>
  </li>
  ...
</ul>

次に CSS を。SVG 用の CSS も一緒に書いていきます。
どうでもいい部分は省きます。

.gallery {
  background : #fff;
  list-style : none;
}

/* clearfix */
.gallery:after {
  clear  : both;
  display: block;
  content: "";
}

.gallery li {
  float : left;
  margin: 10px;
  width : 200px;
  height: 200px;
}

.gallery a {
  display        : block;
  width          : 200px;
  height         : 200px;
  text-decoration: none;
}

.gallery span {
  display           : block;
  position          : relative;
  -webkit-transition: opacity linear 0.5s;
  -moz-transition   : opacity linear 0.5s;
  -ms-transition    : opacity linear 0.5s;
  -o-transition     : opacity linear 0.5s;
  transition        : opacity linear 0.5s;
}

/* 小さい点々とぼかしたような効果を出すためのグラデーション */
.gallery span:before {
  position         : absolute;
  top              : 0;
  left             : 0;
  z-index          : 9;
  width            : 200px;
  height           : 200px;
  background-image : -webkit-radial-gradient(center, circle, rgba(0, 0, 0, 0.1) 1px, transparent 1px),
                     -webkit-radial-gradient(center, circle, transparent 40%, rgba(0, 0, 0, 0.5));
  background-image : -moz-radial-gradient(center, circle, rgba(0, 0, 0, 0.1) 1px, transparent 1px),
                     -moz-radial-gradient(center, circle, transparent 40%, rgba(0, 0, 0, 0.5));
  background-image : -ms-radial-gradient(center, circle, rgba(0, 0, 0, 0.1) 1px, transparent 1px),
                     -ms-radial-gradient(center, circle, transparent 40%, rgba(0, 0, 0, 0.5));
  background-image : -o-radial-gradient(center, circle, rgba(0, 0, 0, 0.1) 1px, transparent 1px),
                     -o-radial-gradient(center, circle, transparent 40%, rgba(0, 0, 0, 0.5));
  background-image : radial-gradient(circle at center, rgba(0, 0, 0, 0.2) 1px, transparent 1px),
                     radial-gradient(circle at center, transparent 40%, rgba(0, 0, 0, 0.5));
  background-size  : 4px 4px, 100% 100%;
  background-repeat: repeat, no-repeat;
  content          : "";
}

.gallery img {
  /* Chrome 19+ */
  -webkit-filter: grayscale(1);
  /* Firefox 3.5+ */
  filter        : url(filter.svg#grayscale);
  /* Internet Explorer 5.5 - 9.0 */
  filter        : progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);
}

/* マウスオーバーでグレースケール画像を透明に */
.gallery a:hover span {
  opacity: 0;
  filter : progid:DXImageTransform.Microsoft.Alpha(opacity=0);
}

/* Opera 用 */
.gallery svg {
  width : 200px;
  height: 200px;
}

/* Opera 用 */
image {
  filter: url(filter.svg#grayscale);
}

一応、標準のグラデーションの指定は新しい構文にしてあります。

img で指定した画像をグレースケールにするのですが、ここで各ブラウザごとの指定を行います。
CSS フィルタも SVG フィルタも使えない Opera ですが、画像を SVG にすることで SVG フィルタの効果を使うことができます。

filter.svg の中身は次のようになっています。
グレースケール用の指定は Filter Effects 1.0 に載っています。

<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="grayscale">
      <feColorMatrix type="matrix"
                     values="0.2126 0.7152 0.0722 0 0
                             0.2126 0.7152 0.0722 0 0
                             0.2126 0.7152 0.0722 0 0
                             0      0      0      1 0"/>
    </filter>
  </defs>
</svg>

これらのどれも使えないブラウザには canvas による Data URI 変換で対応します。

最後に jQuery を使って色々やります。

jQuery(function ($) {

  // フィルタが使えるかどうかのチェック
  var dummy = document.createElement('div'),
      checkStyle = function (el) {
        return el.currentStyle || document.defaultView.getComputedStyle(el, null);
      },
      svgFlag = false,
      canvasFlag = false;
  document.body.appendChild(dummy);
  if (checkStyle(dummy).webkitTransition !== undefined) {
    // webkitTransition があるかどうか
    // webkitTransition が使えて、webkitFilter があるかどうか
    canvasFlag = checkStyle(dummy).webkitFilter ? false : true;
  } else if (checkStyle(dummy).msTransition !== undefined) {
    // msTransition があるかどうか (IE10 かどうか)
    canvasFlag = true;
  } else if (checkStyle(dummy).OTransition !== undefined) {
    // OTransition がある / Opera かどうか。
    svgFlag = true;
  }
  document.body.removeChild(dummy);
  // チェック終わり

  $('.gallery').find('a').each(function () {
    var img = $(this).find('img').eq(0),
        imgUrl = img.attr('src'),
        imgW = img.width(),
        imgH = img.height();
    // svgFlag は Opera のみ true になる
    if (svgFlag) {
      // 画像を SVG の埋め込み画像に
      var svgImg = '<svg width="' + imgW + '" height="' + imgH + '"><image xlink:href="' + imgUrl + '" width="' + imgW + '" height="' + imgH + '" /></svg>';
      img.replaceWith(svgImg);
    }
    // canvasFlag は webkitFilter のない Webkit と IE10 で true
    if (canvasFlag) {
      // 画像がロードされたら DataURI に変換
      img.load(function () {
        this.src = grayscale(this.src);
      });
    }
    // 元々の画像を a 要素の背景として指定する
    $(this).css('background-image', 'url(' + imgUrl + ')');
  });

  /*!
   * Grayscale Image Hover
   * http://webdesignerwall.com/tutorials/html5-grayscale-image-hover
   */
  function grayscale(src) {
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');
    var imgObj = new Image();
    imgObj.src = src;
    canvas.width = imgObj.width;
    canvas.height = imgObj.height;
    ctx.drawImage(imgObj, 0, 0);
    var imgPixels = ctx.getImageData(0, 0, canvas.width, canvas.height);
    for (var y = 0; y < imgPixels.height; y++) {
      for (var x = 0; x < imgPixels.width; x++) {
        var i = (y * 4) * imgPixels.width + x * 4;
        var avg = (imgPixels.data[i] + imgPixels.data[i + 1] + imgPixels.data[i + 2]) / 3;
        imgPixels.data[i] = avg;
        imgPixels.data[i + 1] = avg;
        imgPixels.data[i + 2] = avg;
      }
    }
    ctx.putImageData(imgPixels, 0, 0, 0, 0, imgPixels.width, imgPixels.height);
    return canvas.toDataURL();
  }

});

コメント入れておいたので、やりたいことは分かると思います。
ブラウザ分岐は適当なので信用しないほうがいいです……。

canvas を使った変換は HTML5 Grayscale Image Hover のコードを使用しました。

IE7 以下では a:hover span の部分が動きませんが、この辺はやりようはあると思います。
それと、画像を span で囲んでいるのは Firefox 以外では擬似要素のみの Transition ができないからです。特に必要なものではありません。

canvas 変換による負担を減らしたかっただけなので、今回はこれで。

Leave a Reply