LoginSignup
6
1

More than 5 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ができたので、次回はサーバー側を作ってみます。(気が向いたら)

6
1
0

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
  3. You can use dark theme
What you can do with signing up
6
1