Unformed Building

Grayscale Image Gallery with filter effects

公開:
更新:

パーマリンク

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

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

Grayscale Image Gallery

2012年3月5日 21:24 追記:
Firefoxをご利用の方で、環境によっては、CSSグラデーションをbackground-sizeで小さくしてbackground-repeatで敷き詰めると、gfx.direct2d.disabledがfalseの場合にフリーズするようです(Firefox 10.0.2で確認)。
Firefoxで見ても安全なデモページはこちらです。
本来のデモページから小さな点々を除いたものになります。

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

使うものは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があるかどうか(IE 10かどうか)
    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とIE 10でtrue
    if (canvasFlag) {
      // 画像がロードされたらData URIに変換
      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」のコードを使用しました。

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

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