VueJS 砌体布局

VueJS masonry layout

目前,我正在处理 VueJS 项目及其 vue cli 3。我正在尝试在我的 Vue 项目中实施 MasonryJS,但我被卡住了。我不明白如何为我的 vue 项目实现这种砌体布局。

;
(function(window) {

  /**
   * GridLoaderFx obj.
   */
  function GridLoaderFx(el, options) {
    this.el = el;
    this.items = this.el.querySelectorAll('.grid__item > .grid__link');
  }

  /**
   * Effects.
   */
  GridLoaderFx.prototype.effects = {

    'Shu': {
      lineDrawing: true,
      animeLineDrawingOpts: {
        duration: 800,
        delay: function(t, i) {
          return i * 150;
        },
        easing: 'easeInOutSine',
        strokeDashoffset: [anime.setDashoffset, 0],
        opacity: [{
            value: [0, 1]
          },
          {
            value: [1, 0],
            duration: 200,
            easing: 'linear',
            delay: 500
          }
        ]
      },
      animeOpts: {
        duration: 800,
        easing: [0.2, 1, 0.3, 1],
        delay: function(t, i) {
          return i * 150 + 800;
        },
        opacity: {
          value: [0, 1],
          easing: 'linear'
        },
        scale: [0.5, 1]
      }
    }
  };

  GridLoaderFx.prototype._render = function(effect) {
    // Reset styles.
    this._resetStyles();

    var self = this,
      effectSettings = this.effects[effect],
      animeOpts = effectSettings.animeOpts

    if (effectSettings.perspective != undefined) {
      [].slice.call(this.items).forEach(function(item) {
        item.parentNode.style.WebkitPerspective = item.parentNode.style.perspective = effectSettings.perspective + 'px';
      });
    }

    if (effectSettings.origin != undefined) {
      [].slice.call(this.items).forEach(function(item) {
        item.style.WebkitTransformOrigin = item.style.transformOrigin = effectSettings.origin;
      });
    }

    if (effectSettings.lineDrawing != undefined) {
      [].slice.call(this.items).forEach(function(item) {
        // Create SVG.
        var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
          path = document.createElementNS('http://www.w3.org/2000/svg', 'path'),
          itemW = item.offsetWidth,
          itemH = item.offsetHeight;

        svg.setAttribute('width', itemW + 'px');
        svg.setAttribute('height', itemH + 'px');
        svg.setAttribute('viewBox', '0 0 ' + itemW + ' ' + itemH);
        svg.setAttribute('class', 'grid__deco');
        path.setAttribute('d', 'M0,0 l' + itemW + ',0 0,' + itemH + ' -' + itemW + ',0 0,-' + itemH);
        path.setAttribute('stroke-dashoffset', anime.setDashoffset(path));
        svg.appendChild(path);
        item.parentNode.appendChild(svg);
      });

      var animeLineDrawingOpts = effectSettings.animeLineDrawingOpts;
      animeLineDrawingOpts.targets = this.el.querySelectorAll('.grid__deco > path');
      anime.remove(animeLineDrawingOpts.targets);
      anime(animeLineDrawingOpts);
    }

    if (effectSettings.revealer != undefined) {
      [].slice.call(this.items).forEach(function(item) {
        var revealer = document.createElement('div');
        revealer.className = 'grid__reveal';
        if (effectSettings.revealerOrigin != undefined) {
          revealer.style.transformOrigin = effectSettings.revealerOrigin;
        }
        if (effectSettings.revealerColor != undefined) {
          revealer.style.backgroundColor = effectSettings.revealerColor;
        }
        item.parentNode.appendChild(revealer);
      });

      var animeRevealerOpts = effectSettings.animeRevealerOpts;
      animeRevealerOpts.targets = this.el.querySelectorAll('.grid__reveal');
      animeRevealerOpts.begin = function(obj) {
        for (var i = 0, len = obj.animatables.length; i < len; ++i) {
          obj.animatables[i].target.style.opacity = 1;
        }
      };
      anime.remove(animeRevealerOpts.targets);
      anime(animeRevealerOpts);
    }

    if (effectSettings.itemOverflowHidden) {
      [].slice.call(this.items).forEach(function(item) {
        item.parentNode.style.overflow = 'hidden';
      });
    }

    animeOpts.targets = effectSettings.sortTargetsFn && typeof effectSettings.sortTargetsFn === 'function' ? [].slice.call(this.items).sort(effectSettings.sortTargetsFn) : this.items;
    anime.remove(animeOpts.targets);
    anime(animeOpts);
  };

  GridLoaderFx.prototype._resetStyles = function() {
    this.el.style.WebkitPerspective = this.el.style.perspective = 'none';
    [].slice.call(this.items).forEach(function(item) {
      var gItem = item.parentNode;
      item.style.opacity = 0;
      item.style.WebkitTransformOrigin = item.style.transformOrigin = '50% 50%';
      item.style.transform = 'none';

      var svg = item.parentNode.querySelector('svg.grid__deco');
      if (svg) {
        gItem.removeChild(svg);
      }

      var revealer = item.parentNode.querySelector('.grid__reveal');
      if (revealer) {
        gItem.removeChild(revealer);
      }

      gItem.style.overflow = '';
    });
  };

  window.GridLoaderFx = GridLoaderFx;

  var body = document.body,
    grids = [].slice.call(document.querySelectorAll('.grid')),
    masonry = [],
    currentGrid = 0,
    // Switch grid radio buttons.
    switchGridCtrls = [].slice.call(document.querySelectorAll('.control__radio')),
    // Choose effect buttons.
    fxCtrls = [].slice.call(document.querySelectorAll('.control--effects > .control__btn')),
    // The GridLoaderFx instances.
    loaders = [],
    loadingTimeout;

  function init() {
    // Preload images
    imagesLoaded(body, function() {
      // Initialize Masonry on each grid.
      grids.forEach(function(grid) {
        var m = new Masonry(grid, {
          itemSelector: '.grid__item',
          columnWidth: '.grid__sizer',
          percentPosition: true,
          transitionDuration: 0
        });
        masonry.push(m);
        // Hide the grid.
        grid.classList.add('grid--hidden');
        // Init GridLoaderFx.
        loaders.push(new GridLoaderFx(grid));
      });

      // Show current grid.
      grids[currentGrid].classList.remove('grid--hidden');
      // Init/Bind events.
      initEvents();
      // Remove loading class from body
      body.classList.remove('loading');
      loaders[currentGrid]._render('Shu');
    });
  }

  function initEvents() {
    // Switching grids radio buttons.
    switchGridCtrls.forEach(function(ctrl) {
      ctrl.addEventListener('click', switchGrid);
    });
    // Effect selection.
    fxCtrls.forEach(function(ctrl) {
      ctrl.addEventListener('click', applyFx);
    });
  }


  function applyFx(ev) {
    // Simulate loading grid to show the effect.
    clearTimeout(loadingTimeout);
    grids[currentGrid].classList.add('grid--loading');

    loadingTimeout = setTimeout(function() {
      grids[currentGrid].classList.remove('grid--loading');

      // Apply effect.
      loaders[currentGrid]._render(ev.target.getAttribute('data-fx'));
    }, 500);
  }

  init();

})(window);
.js .loading::before,
.js .loading::after {
  content: '';
  position: fixed;
  z-index: 1000;
}

.loading::before {
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #2c2d31;
}

.loading::after {
  top: 50%;
  left: 50%;
  width: 40px;
  height: 40px;
  margin: -20px 0 0 -20px;
  border: 8px solid #383a41;
  border-bottom-color: #565963;
  border-radius: 50%;
  animation: animLoader 0.8s linear infinite forwards;
}

@keyframes animLoader {
  to {
    transform: rotate(360deg);
  }
}

a {
  text-decoration: none;
  color: #f2f2f2;
  outline: none;
}

.hidden {
  position: absolute;
  overflow: hidden;
  width: 0;
  height: 0;
  pointer-events: none;
}


/* Icons */

.content--side {
  position: relative;
  z-index: 100;
  width: 15vw;
  min-width: 130px;
  max-height: 100vh;
  padding: 0 1em;
  order: 2;
}

.content--center {
  flex: 1;
  max-width: 100vw;
}

.content--related {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  width: 100%;
  padding: 8em 1em 3em;
  text-align: center;
  order: 5;
}

.media-related {
  width: 100%;
}

.media-item {
  padding: 1em;
}

.media-item__img {
  max-width: 100%;
  /*opacity: 0.7;*/
  /*transition: opacity 0.3s;*/
}

.media-item:hover .media-item__img,
.media-item:focus .media-item__img {
  opacity: 1;
}

.media-item__title {
  font-size: 1em;
  max-width: 220px;
  padding: 0.5em;
  margin: 0 auto;
}

@keyframes octocat-wave {
  0%,
  100% {
    transform: rotate(0);
  }
  20%,
  60% {
    transform: rotate(-25deg);
  }
  40%,
  80% {
    transform: rotate(10deg);
  }
}


/* Grid */

.grid {
  position: relative;
  z-index: 1;
  display: block;
  margin: 2% 5%;
}

.grid--hidden {
  position: fixed !important;
  z-index: 1;
  top: 0;
  left: 0;
  width: 100%;
  pointer-events: none;
  opacity: 0;
}

.js .grid--loading::before,
.js .grid--loading::after {
  content: '';
  z-index: 1000;
}

.js .grid--loading::before {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: #2c2d31;
}

.js .grid--loading::after {
  position: absolute;
  top: calc(25vh - 20px);
  left: 50%;
  width: 40px;
  height: 40px;
  margin: 0 0 0 -20px;
  border: 8px solid #383a41;
  border-bottom-color: #565963;
  border-radius: 50%;
  animation: animLoader 0.8s linear forwards infinite;
}

.grid__sizer {
  margin-bottom: 0 !important;
}

.grid__link,
.grid__img {
  display: block;
}

.grid__img {
  width: 100%;
}

.grid__deco {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
}

.grid__deco path {
  fill: none;
  stroke: #fff;
  stroke-width: 2px;
}

.grid__reveal {
  position: absolute;
  z-index: 50;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  opacity: 0;
  background-color: #2c2d31;
}

.grid__item {}

.grid__item:hover>.cm-pic-author {
  opacity: 1;
}

.grid__item:hover>.cm-pic-social {
  opacity: 1;
}

.cm-pic-social {
  transition: opacity 0.6s ease-in-out;
  position: absolute;
  right: 10px;
  top: 10px;
  opacity: 0;
}

.cm-pic-social a {
  color: #ffffff;
  font-family: 'Roboto', sans-serif;
  text-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
  font-size: .9em;
  text-decoration: none;
}

.cm-pic-social a:not(:last-child) {
  margin-right: 1em;
}

.cm-pic-author {
  transition: opacity 0.6s ease-in-out;
  position: absolute;
  left: 10px;
  bottom: 10px;
  opacity: 0;
}

.cm-pic-author a {
  font-family: 'Roboto', sans-serif;
  font-size: .9em;
  text-decoration: none;
  color: #ffffff;
  text-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
}

.cm-pic-author a img {
  width: 35px;
  height: 35px;
  object-position: center;
  object-fit: cover;
  border: 1px solid #ffffff;
  -webkit-border-radius: 50%;
  -moz-border-radius: 50%;
  border-radius: 50%;
  margin-right: 10px;
}

.grid .grid__item,
.grid .grid__sizer {
  width: calc(100% - 20px);
  margin: 0 10px 20px;
}

@media only screen and (min-width: 576px) {
  .grid .grid__item,
  .grid .grid__sizer {
    width: calc((100% / 2) - 20px);
    margin: 0 10px 20px;
  }
}


/* min-width 1200px, large screens */

@media only screen and (min-width: 1200px) {
  .grid .grid__item,
  .grid .grid__sizer {
    width: calc((100% / 3) - 20px);
    margin: 0 10px 20px;
  }
}


/* min-width 1500px, xlarge screens */

@media only screen and (min-width: 1500px) {
  /* Grid types */
  .grid-masonry .grid__item,
  .grid-masonry .grid__sizer {
    width: calc(25% - 20px);
    margin: 0 10px 20px;
  }
}


/* min-width 1800px, xlarge screens */

@media only screen and (min-width: 1800px) {
  /* Grid types */
  .grid-masonry .grid__item,
  .grid-masonry .grid__sizer {
    width: calc(20% - 20px);
    margin: 0 10px 20px;
  }
}


/*!* min-width 2400px, xlarge screens 5k*!*/


/*@media only screen and (min-width: 2400px){*/


/*!* Grid types *!*/


/*.grid-masonry .grid__item,*/


/*.grid-masonry .grid__sizer {*/


/*width: calc(16.666666% - 20px);*/


/*margin: 0 10px 20px;*/


/*}*/


/*}*/
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/2.2.0/anime.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/masonry/4.2.2/masonry.pkgd.min.js"></script>
<script src="https://unpkg.com/imagesloaded@4/imagesloaded.pkgd.min.js"></script>
<div class="content content--center">
  <div class="grid grid-masonry">
    <div class="grid__sizer"></div>
    <div class="grid__item">
      <a class="grid__link" href="image-details.html">
        <img class="grid__img" src="https://i.imgur.com/dCFlYyG.jpg" alt="Some image" />
      </a>

      <div class="cm-pic-social d-flex">
        <a href=""><span><img width="16px" src="images/add.svg" alt=""></span> Collection</a>
        <a href=""><span><img width="16px" src="images/love.svg" alt=""></span> 50</a>
      </div>

      <div class="cm-pic-author d-flex">
        <a href="personal-profile-follow.html"><img src="images/author@2x.png" alt="">J. Alexa</a>
      </div>

    </div>
    <div class="grid__item">
      <a class="grid__link" href="#"><img class="grid__img" src="https://i.imgur.com/Zneml4H.jpg" alt="Some image" /></a>

      <div class="cm-pic-social d-flex">
        <a href=""><span><img width="16px" src="images/add.svg" alt=""></span> Collection</a>
        <a href=""><span><img width="16px" src="images/love.svg" alt=""></span> 50</a>
      </div>

      <div class="cm-pic-author d-flex">
        <a href=""><img src="images/author@2x.png" alt="">J. Alexa</a>
      </div>

    </div>
    <div class="grid__item">
      <a class="grid__link" href="#"><img class="grid__img" src="https://i.imgur.com/H4bbqpA.jpg" alt="Some image" /></a>
      <div class="cm-pic-social d-flex">
        <a href=""><span><img width="16px" src="images/add.svg" alt=""></span> Collection</a>
        <a href=""><span><img width="16px" src="images/love.svg" alt=""></span> 50</a>
      </div>

      <div class="cm-pic-author d-flex">
        <a href=""><img src="images/author@2x.png" alt="">J. Alexa</a>
      </div>
    </div>
    <div class="grid__item">
      <a class="grid__link" href="#"><img class="grid__img" src="https://i.imgur.com/9Q9pgmR.jpg" alt="Some image" /></a>
      <div class="cm-pic-social d-flex">
        <a href=""><span><img width="16px" src="images/add.svg" alt=""></span> Collection</a>
        <a href=""><span><img width="16px" src="images/love.svg" alt=""></span> 50</a>
      </div>

      <div class="cm-pic-author d-flex">
        <a href=""><img src="https://i.imgur.com/9Q9pgmR.jpg" alt="">J. Alexa</a>
      </div>
    </div>
    <div class="grid__item">
      <a class="grid__link" href="#"><img class="grid__img" src="https://picsum.photos/600/800" alt="Some image" /></a>
      <div class="cm-pic-social d-flex">
        <a href=""><span><img width="16px" src="images/add.svg" alt=""></span> Collection</a>
        <a href=""><span><img width="16px" src="images/love.svg" alt=""></span> 50</a>
      </div>

      <div class="cm-pic-author d-flex">
        <a href=""><img src="images/author@2x.png" alt="">J. Alexa</a>
      </div>
    </div>
    <div class="grid__item">
      <a class="grid__link" href="image-details.html">
        <img class="grid__img" src="https://i.imgur.com/dCFlYyG.jpg" alt="Some image" />
      </a>

      <div class="cm-pic-social d-flex">
        <a href=""><span><img width="16px" src="images/add.svg" alt=""></span> Collection</a>
        <a href=""><span><img width="16px" src="images/love.svg" alt=""></span> 50</a>
      </div>

      <div class="cm-pic-author d-flex">
        <a href="personal-profile-follow.html"><img src="images/author@2x.png" alt="">J. Alexa</a>
      </div>

    </div>
    <div class="grid__item">
      <a class="grid__link" href="#"><img class="grid__img" src="https://i.imgur.com/Zneml4H.jpg" alt="Some image" /></a>

      <div class="cm-pic-social d-flex">
        <a href=""><span><img width="16px" src="images/add.svg" alt=""></span> Collection</a>
        <a href=""><span><img width="16px" src="images/love.svg" alt=""></span> 50</a>
      </div>

      <div class="cm-pic-author d-flex">
        <a href=""><img src="images/author@2x.png" alt="">J. Alexa</a>
      </div>

    </div>
    <div class="grid__item">
      <a class="grid__link" href="#"><img class="grid__img" src="https://i.imgur.com/H4bbqpA.jpg" alt="Some image" /></a>
      <div class="cm-pic-social d-flex">
        <a href=""><span><img width="16px" src="images/add.svg" alt=""></span> Collection</a>
        <a href=""><span><img width="16px" src="images/love.svg" alt=""></span> 50</a>
      </div>

      <div class="cm-pic-author d-flex">
        <a href=""><img src="images/author@2x.png" alt="">J. Alexa</a>
      </div>
    </div>
    <div class="grid__item">
      <a class="grid__link" href="#"><img class="grid__img" src="https://i.imgur.com/9Q9pgmR.jpg" alt="Some image" /></a>
      <div class="cm-pic-social d-flex">
        <a href=""><span><img width="16px" src="images/add.svg" alt=""></span> Collection</a>
        <a href=""><span><img width="16px" src="images/love.svg" alt=""></span> 50</a>
      </div>

      <div class="cm-pic-author d-flex">
        <a href=""><img src="https://i.imgur.com/9Q9pgmR.jpg" alt="">J. Alexa</a>
      </div>
    </div>
    <div class="grid__item">
      <a class="grid__link" href="#"><img class="grid__img" src="https://picsum.photos/600/800" alt="Some image" /></a>
      <div class="cm-pic-social d-flex">
        <a href=""><span><img width="16px" src="images/add.svg" alt=""></span> Collection</a>
        <a href=""><span><img width="16px" src="images/love.svg" alt=""></span> 50</a>
      </div>

      <div class="cm-pic-author d-flex">
        <a href=""><img src="images/author@2x.png" alt="">J. Alexa</a>
      </div>
    </div>

  </div>
</div>

现在我正在尝试将它实现为一个 vue 组件,以便我可以在任何视图页面上呈现。

谢谢

可以通过npm安装

npm install masonry-layout --save

然后将其导入到您的组件(如果您愿意,也可以导入到全局)

组件

import Masonry from "masonry-layout";

export default {

    mounted: function () {

        // initialization of masonry

        var grid = document.querySelector('.masonry-grid');
        var msnry = new Masonry( grid, {
            // options...
            columnWidth: '.masonry-grid-sizer',
            itemSelector: '.masonry-grid-item',
            percentPosition: true
        });
    }
}

模板

  <template>
   <div>
    <!-- Blog Masonry Blocks -->
    <div class="container ">
        <div class="masonry-grid row ">
            <div class="masonry-grid-sizer col-sm-1"></div>

            <div class="masonry-grid-item col-lg-3">
                ...
            </div>

            <div class="masonry-grid-item col-lg-3">
                ...
            </div>

            <div class="masonry-grid-item col-lg-3">
                ...
            </div>
        </div>
    </div>
  </div>
</template>

尝试安装 npm install vue-masonry-css --save-dev 很容易实现。 https://github.com/paulcollett/vue-masonry-css

<masonry
  :cols="3"
  :gutter="30"
  >
  <div v-for="(item, index) in items" :key="index">Item: {{index + 1}}</div>
</masonry>