LoginSignup
1

More than 3 years have passed since last update.

どうぶつフォトコンテストアプリを作る(PinterestライクなUI編)

Posted at

何となくどうぶつフォトコンテストアプリを作りたくなったので、PinterestライクなUIをMonaca IDEでサクッと作ります。

こんなの

ファイル_001.png

ライブラリはあるけど・・

Masonry
https://masonry.desandro.com/

スマホだから2カラムで良いし、何か大袈裟な気がする。

CSSで作る

まずはCSSで作ってみます。
段組みレイアウト(CSS Columns)を使えばよさそうです。

ons.bootstrap()
  .controller('PhotoController', function() {
    this.photos = [
      {
        url: './img/214hariIMGL9360_TP_V.jpg',
        text: '1.かわいいやろ?かわいいやろ?かわいいやろ?',
        imageHeight: '200px'
      },
      {
        url: './img/214hariIMGL9876_TP_V.jpg',
        text: '2.かわいいやろ?かわいいやろ?かわいいやろ?かわいいやろ?かわいいやろ?',
        imageHeight: '600px'
      },
      {
        url: './img/C784_edawokuwaerugorira_TP_V.jpg',
        text: '3.かわいいやろ?かわいいやろ?かわいいやろ?',
        imageHeight: '180px'
      },
      {
        url: './img/inosisiFTHG2811_TP_V.jpg',
        text: '4.かわいいやろ?かわいいやろ?かわいいやろ?かわいいやろ?',
        imageHeight: '150px'
      },
      {
        url: './img/miyaDSC_4461_TP_V.jpg',
        text: '5.かわいいやろ?かわいいやろ?かわいいやろ?かわいいやろ?',
        imageHeight: '240px'
      },
      {
        url: './img/nuko-9_TP_V.jpg',
        text: '6.かわいいやろ?',
        imageHeight: '300px'
      },
      {
        url: 'https://pbs.twimg.com/profile_images/581025665727655936/9CnwZZ6j.jpg',
        text: '7.かわいいやろ?かわいいやろ?かわいいやろ?',
        imageHeight: '300px'
      }
    ];
  });
<ons-page ng-controller="PhotoController as vm">
  <div class="header">動物フォトコンテスト開催中</div>

  <div class="photos">
    <div class="photo" ng-repeat="photo in vm.photos">
      <div class="photo-image" ng-style="{ backgroundImage: 'url('+ photo.url +')', height: photo.imageHeight }"></div>
      <div class="photo-text">{{ photo.text }}</div>
    </div>
  </div>
</ons-page>
.header {
  padding: 16px;
  background-color: white;
  text-align: center;
  font-size: 24px;
  font-weight: bold;
}

.photos {
  -webkit-column-count: 2;
  column-count: 2;
  -webkit-column-gap: 30px;
  column-gap: 8px;
  padding: 8px;
}

.photo {
  -webkit-column-break-inside: avoid;
  page-break-inside: avoid;
  break-inside: avoid;
  display: inline-block;
  width: 100%;
  margin-bottom: 8px;
  background-color: white;
  border-radius: 12px;
}

.photo-image {
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
  cursor: zoom-in;
  border-top-left-radius: 12px;
  border-top-right-radius: 12px;
}

.photo-text {
  padding: 8px;
}

キャプチャ.PNG

でもこれだと、1カラム目を描画してから、2カラム目を描画していて、コレジャナイ感があります。

Pinterestを観察

ちゃんと高さが低いカラムから埋めてますね。
これがやりたいんですよ。

キャプチャ.PNG

次にソースコードを見て、どんな感じに実装しているのか観察します。
すべての要素をposition: absolute; left: 0; top: 0;で左上に固定して、そこからtransform: translateX(**px) translateY(**px)で配置しているようです。

キャプチャ.PNG

JSで作る

この方法だとJSで作る必要が有ります。
今回はAngularJSで作ります。汎用性を考えて、ディレクティブで作ります。

ons.bootstrap()
  .controller('PhotoController', function($scope, $timeout) {
    // CSSで作った、と同じため省略…
  }).directive('pinterestLikeGrid', function () {
    return {
      restrict: 'E',
      transclude: true,
      replace: true,
      template: '<div class="pinterest-like-grid" ng-transclude></div>',
      scope: {
        gutterWidth: '@',
        gutterHeight: '@'
      },
      link: function (scope, element, attrs) {
        var gutterWidth = parseInt(attrs.gutterWidth, 10) || 8;
        var gutterHeight = parseInt(attrs.gutterHeight, 10) || 8;
        var photosElem = element[0];
        scope.$watch(function () {
          return photosElem.childNodes.length; // 子要素の件数が変わったらレイアウトを調整
        }, function() {
          // 2カラムなので、親の横幅÷2。余白分も差っ引く
          var width = (photosElem.offsetWidth - gutterWidth*3) / 2;
          var photoElems = photosElem.querySelectorAll('.pinterest-like-grid-item');

          var firstColHeight = gutterHeight;
          var secondColHeight = gutterHeight;
          // 子要素を1件ずつ配置していく
          photoElems.forEach(function (photoElem) {
            photoElem.style.width = width + 'px';

            var height = photoElem.offsetHeight;
            var x, y;
            // 高さの低い方のカラムに配置する
            if (firstColHeight <= secondColHeight) {
              // 1カラム目
              x = gutterWidth;
              y = firstColHeight;
              firstColHeight = firstColHeight + height + gutterHeight;
            } else {
              // 2カラム目
              x = width+gutterWidth*2;
              y = secondColHeight;
              secondColHeight = secondColHeight + height + gutterHeight;
            }
            photoElem.style.transform = photoElem.style.webkitTransform = 'translateX(' + x + 'px) translateY(' + y + 'px)';
            photoElem.style.height = height + 'px';
          });
        });
      }
    };
  }).directive('pinterestLikeGridItem', function () {
    return {
      restrict: 'E',
      transclude: true,
      replace: true,
      template: '<div class="pinterest-like-grid-item" ng-transclude></div>'
    };
  });
<ons-page ng-controller="PhotoController as vm">
  <div class="header">動物フォトコンテスト開催中</div>

  <pinterest-like-grid gutter-width="12" gutter-height="12">
    <pinterest-like-grid-item ng-repeat="photo in vm.photos">
      <div class="pinterest-like-grid-item-image" ng-style="{ backgroundImage: 'url('+ photo.url +')', height: photo.imageHeight }"></div>
      <div class="pinterest-like-grid-item-text">{{ photo.text }}</div>
    </pinterest-like-grid-item>
  </pinterest-like-grid>
</ons-page>
.header {
  padding: 16px;
  background-color: white;
  text-align: center;
  font-size: 24px;
  font-weight: bold;
}

.pinterest-like-grid {
  position: relative;
}

.pinterest-like-grid-item {
  -webkit-column-break-inside: avoid;
  page-break-inside: avoid;
  break-inside: avoid;
  display: inline-block;
  width: 100%;
  margin-bottom: 8px;
  background-color: white;
  border-radius: 12px;
  position: absolute;
  left: 0;
  top: 0;
}

.pinterest-like-grid-item-image {
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
  cursor: zoom-in;
  border-top-left-radius: 12px;
  border-top-right-radius: 12px;
}

.pinterest-like-grid-item-text {
  padding: 8px;
}

ポイントとしては、widthのスタイルを変更してから、高さをoffsetHeightで取得している点です。
widthを変える前は1/1カラムなので、widthを1/2カラム分の変更してから高さを取得します。

後は、transform には、translateXとtranslateYだけ設定します。translateZを0にするとiOSのMobile Safafiでスクロールした際に画面がチラつきます。(Safafiのバグっぽい)

ちなみに、topとleftの位置を変更しても良いですが、transformの方がGPUレイヤーで描画されるので速そうです。(検証はしてない)

画面

キャプチャ.PNG

うん、なんかいい感じ。

次回へ続く

UIができたので、次回はサーバー側を作ってみます。(気が向いたら)

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1