【更新日 : 】
【脱Jquery】FlexboxとVanilla JSで実装する簡易Masonryレイアウト
- Category:
- css / js
- Tags:
- css, GSAP, JavaScript, Vanilla JS, アニメーション, 脱Jquery, 自作関数
FlexboxとVanilla JSで実装した簡易Masonryレイアウトのサンプルです。
ランダム配置なし。横の並び順は維持されます。
ソースコード
デモ
See the Pen Simple Masonry Flexbox & Vanilla JS by web_nak (@web_walking_nak) on CodePen.
使い方
CSSの読み込み
<link rel="stylesheet" href="file-path/simple-masonry.css">
もしくは以下styleを追加してください。
/* Simple Masonry Flexbox & Vanilla JS ベース設定 */
.js-masonry-list {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
}
@media only screen and (min-width: 1025px) {
.js-masonry-list {
column-gap: 2.5%;
}
.js-masonry-elm {
width: 18%;
margin-bottom: 2.5%;
}
}
@media only screen and (min-width: 601px) and (max-width: 1024px) {
.js-masonry-list {
column-gap: 1.25%;
}
.js-masonry-elm {
width: 32.5%;
margin-bottom: 1.25%;
}
}
@media only screen and (min-width: 451px) and (max-width: 600px) {
.js-masonry-list {
column-gap: 2%;
}
.js-masonry-elm {
width: 49%;
margin-bottom: 2%;
}
}
@media only screen and (max-width: 450px) {
.js-masonry-elm {
width: 100%;
margin-bottom: 20px;
}
}
JavaScriptの読み込み
<script src="file-path/simple-masonry.min.js"></script>
※IE11へ対応する場合は simple-masonry-ie11.min.js を読み込んでください。
<script src="file-path/simple-masonry-ie11.min.js"></script>
もしくは以下のコードを追加してください。
//Simple Masonry Flexbox & Vanilla JS
function masonry(setOptions) {
'use strict';
//options
const defaultOptions = {
target: '.js-masonry-list', //対象リスト
column: 1, //カラム数
responsive: null,
/*
//レスポンシブ設定
responsive: [{
breakpoint: 1024, //ブレイクポイント max-width
column: 3
}, {
breakpoint: 500,
column: 1
}]
*/
activeClass: 'is-active', //Masonry active class
listClass: '.js-masonry-list', //対象リスト class
listElmsClass: '.js-masonry-elm', //対象リスト内要素 class
}
//設定をマージ
const options = Object.assign({}, defaultOptions, setOptions);
//class設定
const listClass = options.listClass;
const listElmsClass = options.listElmsClass;
//対象リスト取得
const lists = Array.prototype.slice.call(document.querySelectorAll(listClass),0);
//表示エリアが存在するかチェック
if(lists.length === 0) {
return false;
}
//masonry関数呼び出し
masonryFunk(options);
window.addEventListener('resize', function() {
masonryFunk(options);
});
//masonry関数
function masonryFunk(options) {
//カラム数チェック
let column = options.column;
//レスポンシブ切り替え
const responsive = options.responsive;
if(responsive) {
//ウィンドウの幅取得
const winWidth = window.innerWidth;
//ウィンドウの幅とブレイクポイントを比較
for (let i = 0; i < responsive.length; i++) {
if(winWidth <= responsive[i].breakpoint) {
column = responsive[i].column;
}
}
}
//1カラムの場合は設定をリセットし処理を終了
if(column === 1) {
lists.forEach(function(list){
const listElms = Array.prototype.slice.call(list.querySelectorAll(listElmsClass),0);
listElms.forEach(function(listElm){
//設定リセット
listElm.style.marginTop = '';
});
});
return false;
}
//対象リストごとの設定
lists.forEach(function(list){
//対象リスト内要素取得
const listElms = Array.prototype.slice.call(list.querySelectorAll(listElmsClass),0);
//対象リスト内要が存在するかチェック
if(listElms.length === 0) {
return false;
}
//初期設定
if(!list.classList.contains(options.activeClass)) {
//active class設定
list.classList.add(options.activeClass);
}
//要素の位置
listElms.forEach(function(listElm,index){
//設定リセット
listElm.style.marginTop = '';
//最初の行は処理しない
if(column > index) {
return;
}
//直上の要素取得
const topListElm = listElms[index-column];
//直上の要素の位置取得
const topListElmPosi = topListElm.getBoundingClientRect().top;
//直上の要素のmargin-bottomを含む高さ取得
const topListHeight = getHeightAndMarginBottom(topListElm);
//直上の要素の下端位置取得
const topListBottomPosi = topListElmPosi + topListHeight;
//表示エリアの位置取得
const listElmPosi = listElm.getBoundingClientRect().top;
//調整位置取得
let setPosi = listElmPosi.toFixed(0) - topListBottomPosi.toFixed(0);
//0の場合処理しない
if(setPosi === 0) {
return false;
}
//位置をズラす
setPosi = '-' + setPosi + 'px';
listElm.style.marginTop = setPosi;
});
});
}
//margin-bottomを含む高さ
function getHeightAndMarginBottom(elm) {
//高さ取得
const height = elm.getBoundingClientRect().height;
//margin取得
const styles = getComputedStyle(elm);
const bottom = parseFloat(styles.marginBottom);
return height + bottom;
}
}
HTMLにレイアウトコードを追加
<ul class="js-masonry-list">
<li class="js-masonry-elm">
<img src="https://picsum.photos/id/1003/300/400" alt="" width="300" height="400">
</li>
</ul>
Masonry呼び出し
masonry({
target: '.js-masonry-list', //対象リスト
column: 5, //カラム数
responsive: [{
breakpoint: 1024, //ブレイクポイント max-width
column: 3
}, {
breakpoint: 600,
column: 2
}, {
breakpoint: 450,
column: 1
}]
});
レイアウト設定について
横並びレイアウトはCSSで作成しますので、JS側で設定するブレイクポイントはCSSで設定した値を入れてください。
オプション
masonry({
target: '.js-masonry-list', //対象リスト
column: 1, //カラム数
responsive: null,
/*
//レスポンシブ設定
responsive: [{
breakpoint: 1024, //ブレイクポイント max-width
column: 3
}, {
breakpoint: 500,
column: 1
}]
*/
activeClass: 'is-active', //Masonry active class
listClass: '.js-masonry-list', //対象リスト class
listElmsClass: '.js-masonry-elm', //対象リスト内要素 class
});
動作環境
- Internet Explorer 11
- Google Chrome 最新版
- Firefox 最新版
- Safari 最新版
【全てJSで実装する版】
上記をベースに何もかも全部JSでやる版も作成してみました。
こちらの方が手軽に実装できるメリットはありますが、CSSを使わないので細かい微調整はニガテ+JSが読まれるまでレイアウトが崩れるデメリットもあります。
ソースコード
デモ
See the Pen Simple Masonry Flexbox & Vanilla JS (not use css) by web_nak (@web_walking_nak) on CodePen.
使い方
JS読み込み
<script src="file-path/simple-masonry.min.js"></script>
※IE11へ対応する場合は simple-masonry-ie11.min.js を読み込んでください。
<script src="file-path/simple-masonry-ie11.min.js"></script>
もしくは以下のコードを追加してください。
//Simple Masonry Flexbox & Vanilla JS (not use css)
function masonry(setOptions) {
'use strict';
//options
const defaultOptions = {
target: '.js-masonry-list', //対象リスト
column: 1, //カラム数
columnGap: 0, //number:対象リスト内要素の横余白(相対値(%)に置き換えられます)
rowGap: 0, //number or string:対象リスト内要素の下余白(相対値(%)に置き換えられます。※置き換えたくない場合は、'20%'など単位を入れてください。)
responsive: null,
/*
//レスポンシブ設定
responsive: [
{
breakpoint: 1024, //ブレイクポイント max-width
column: 3,
columnGap: 50,
rowGap: 50,
}, {
breakpoint: 500,
column: 1,
columnGap: 0,
rowGap: 50,
}
]
*/
activeClass: 'is-active', //Masonry active class
listClass: '.js-masonry-list', //対象リスト class
listElmsClass: '.js-masonry-elm', //対象リスト内要素 class
}
//設定をマージ
const options = Object.assign({}, defaultOptions, setOptions);
//class設定
const listClass = options.listClass;
const listElmsClass = options.listElmsClass;
//対象リスト取得
const lists = Array.prototype.slice.call(document.querySelectorAll(listClass),0);
//表示エリアが存在するかチェック
if(lists.length === 0) {
return false;
}
//masonry関数呼び出し
masonryFunk(options);
window.addEventListener('resize', function() {
masonryFunk(options);
});
//masonry関数
function masonryFunk(options) {
//カラム、余白チェック
let column = options.column;
let columnGap = options.columnGap;
let rowGap = options.rowGap;
let listWidth = null;
//レスポンシブ切り替え
const responsive = options.responsive;
if(responsive) {
//ウィンドウの幅取得
const winWidth = window.innerWidth;
//ウィンドウの幅とブレイクポイントを比較
for (let i = 0; i < responsive.length; i++) {
if(winWidth <= responsive[i].breakpoint) {
column = responsive[i].column;
columnGap = responsive[i].columnGap;
rowGap = responsive[i].rowGap;
listWidth = responsive[i].breakpoint;
}
}
}
//対象リストごとの設定
lists.forEach(function(list){
//対象リスト内要素取得
const listElms = Array.prototype.slice.call(list.querySelectorAll(listElmsClass),0);
//対象リスト内要が存在するかチェック
if(listElms.length === 0) {
return false;
}
//初期設定
if(!list.classList.contains(options.activeClass)) {
//flex box初期設定
list.style.display = 'flex';
list.style.flexWrap = 'wrap';
list.style.alignItems = 'flex-start';
//active class設定
list.classList.add(options.activeClass);
}
//対象リスト内要素の幅設定
//対象リストの幅取得
if(!listWidth) {
listWidth = parseFloat(getComputedStyle(list).width);
}
//左右余白の合計値取得
const columnGapTotal = columnGap * (column-1);
//要素の幅取得(%)
const listElmWidth = (((listWidth - columnGapTotal)/column)/listWidth*100)+'%';
//要素の余白取得(%)
const listColumnGap = (columnGap/listWidth*100)+'%';
//rowGapが数値だった場合は相対値に変換
if(typeof rowGap === 'number') {
rowGap = (rowGap/listWidth*100)+'%';
}
//要素の位置
listElms.forEach(function(listElm,index){
//幅・余白設定
listElm.style.marginRight = listColumnGap;
listElm.style.width = listElmWidth;
listElm.style.marginBottom = rowGap;
//設定リセット
listElm.style.marginTop = '';
if(column !== 1 && (index + 1) % column === 0) {
listElm.style.marginRight = '';
}
//最初の行は位置調整処理をしない
if(column > index) {
return;
}
//直上の要素取得
const topListElm = listElms[index-column];
//直上の要素の位置取得
const topListElmPosi = topListElm.getBoundingClientRect().top;
//直上の要素のmargin-bottomを含む高さ取得
const topListHeight = getHeightAndMarginBottom(topListElm);
//直上の要素の下端位置取得
const topListBottomPosi = topListElmPosi + topListHeight;
//表示エリアの位置取得
const listElmPosi = listElm.getBoundingClientRect().top;
//調整位置取得
let setPosi = listElmPosi.toFixed(0) - topListBottomPosi.toFixed(0);
//0の場合処理しない
if(setPosi === 0) {
return false;
}
//位置をズラす
setPosi = '-' + setPosi + 'px';
listElm.style.marginTop = setPosi;
});
});
}
//margin-bottomを含む高さ取得関数
function getHeightAndMarginBottom(elm) {
//高さ取得
const height = elm.getBoundingClientRect().height;
//margin取得
const styles = getComputedStyle(elm);
const bottom = parseFloat(styles.marginBottom);
return height + bottom;
}
}
HTMLにレイアウトコードを追加
<ul class="js-masonry-list">
<li class="js-masonry-elm">
<img src="https://picsum.photos/id/1003/300/400" alt="" width="300" height="400">
</li>
</ul>
Masonry呼び出し
masonry({
target: '.js-masonry-list', //対象リスト
column: 5, //カラム数
columnGap: 50, //対象リスト内要素の横余白
rowGap: 50, //対象リスト内要素の下余白
responsive: [ //レスポンシブ設定
{
breakpoint: 1024, //ブレイクポイント max-width
column: 4,
columnGap: 40,
rowGap: 40,
},
{
breakpoint: 800,
column: 3,
columnGap: 30,
rowGap: 30,
},
{
breakpoint: 600,
column: 2,
columnGap: 20,
rowGap: 20,
},
{
breakpoint: 400,
column: 1,
columnGap: 0,
rowGap: 40,
}
]
});
オプション
masonry({
target: '.js-masonry-list', //対象リスト
column: 1, //カラム数
columnGap: 0, //number:対象リスト内要素横の余白(相対値(%)に置き換えられます)
rowGap: 0, //number or string:対象リスト内要素の下余白(相対値(%)に置き換えられます。※置き換えたくない場合は、'20%'など単位を入れてください。)
responsive: null,
/*
//レスポンシブ設定
responsive: [
{
breakpoint: 1024, //ブレイクポイント max-width
column: 3,
columnGap: 50,
rowGap: 50,
}, {
breakpoint: 500,
column: 1,
columnGap: 0,
rowGap: 50,
}
]
*/
activeClass: 'is-active', //Masonry active class
listClass: '.js-masonry-list', //対象リスト class
listElmsClass: '.js-masonry-elm', //対象リスト内要素 class
});