@unri/masonry
A simple Masonry layout javascript function (+ responsive & animation)
🚀 Simple: 1 function to call (no CSS)⚡️ Small: <1ko Gzipped & minified (bundlephobia)📦️ 0 dependencies💫 Animation (optionnal)🛂 Written in Typescript📱 Responsive compatible (see Vanilla JS example or ReactJS example)♾️ Infinite scroll compatible (see Infinite scroll example)
Table of Contents
Installation
in the browser
<script type="module">
import * as Masonry from "https://unpkg.com/@unri/masonry@latest/es/index.js";
...
</script>
with a package manager
npm i @unri/masonry
# or
yarn add @unri/masonry
Usage
Requirements
- Every items in the Masonry layout must have the same width.
- Every items in the Masonry layout should have predetermined height,
otherwise it may overlap (example :
<img loading="lazy" ... />
you should specifywidth
&height
attributes on<img />
).
Vanilla JS
Here is an example with pure javascript. (also example on CodePen).
HTML :
<body>
<div id="masonry-layout">
<div class="card" style="width: 260px; height: 204px;">1</div>
<div class="card" style="width: 260px; height: 424px;">2</div>
<div class="card" style="width: 260px; height: 152px;">3</div>
<div class="card" style="width: 260px; height: 396px;">4</div>
<div class="card" style="width: 260px; height: 294px;">5</div>
<div class="card" style="width: 260px; height: 312px;">6</div>
<div class="card" style="width: 260px; height: 106px;">7</div>
<div class="card" style="width: 260px; height: 270px;">8</div>
<div class="card" style="width: 260px; height: 331px;">9</div>
<div class="card" style="width: 260px; height: 124px;">10</div>
<div class="card" style="width: 260px; height: 271px;">11</div>
<div class="card" style="width: 260px; height: 446px;">12</div>
<div class="card" style="width: 260px; height: 407px;">13</div>
<div class="card" style="width: 260px; height: 144px;">14</div>
<div class="card" style="width: 260px; height: 388px;">15</div>
<div class="card" style="width: 260px; height: 249px;">16</div>
<div class="card" style="width: 260px; height: 120px;">17</div>
<div class="card" style="width: 260px; height: 345px;">18</div>
<div class="card" style="width: 260px; height: 321px;">19</div>
<div class="card" style="width: 260px; height: 285px;">20</div>
<div class="card" style="width: 260px; height: 125px;">21</div>
</div>
</body>
Javascript :
import { positionMasonry } from "https://unpkg.com/@unri/masonry@latest/dist/index.mjs";
import debounce from "utils/debounce.js";
// Get the HTML element that contains the Masonry layout items
const masonryLayout = document.getElementById("masonry-layout");
// Use debounce on for better performances
const debouncePositionMasonry = debounce(() => {
positionMasonry({
// The masonry layout container
rootElement: masonryLayout,
// Horizontal alignment of the masonry layout elements (default to center)
align: "center",
// The gap between the masonry layout elements in pixels (default to 0)
gap: 30,
// or use gapX to specify the horizontal gap between elements (optionnal)
gapX: 30,
// or use gapY to specify the vertical gap between elements (optionnal)
gapY: 30,
// or use pass a function to gapY that takes the number of column in argument
gapY: (numColumns) => (numColumns > 1 ? 30 : 0),
// The duration of the re/positionning of the masonry layout elements (default to 0)
animationDuration: 0.15,
});
}, 200);
// Keep track of the device orientation
let orientation = window.screen.orientation.type;
// Add event listener on resize for responsiveness
window.addEventListener("resize", () => {
const orientationChange = window.screen.orientation.type !== orientation;
orientation = window.screen.orientation.type;
if (!orientationChange) {
debouncePositionMasonry();
} else {
// Position with no delay & no animation (to avoid mobile browser reflow bug)
positionMasonry({
rootElement: masonryLayout,
gap: 30,
align: "center",
});
}
});
// Trigger positioning
positionMasonry({ rootElement: masonryLayout, gap: 30, align: "center" });
ReactJS
Here is an example with ReactJS. (also example on CodePen).
import { positionMasonry } from "@unri/masonry";
import debounce from "utils/debounce";
// Array of random heights
const heights = [ 204, 424, 152, 396, 294, ... ];
function MasonryLayout({ gap, children }) {
// Reference the HTML element that contains the Masonry layout items
const ref = React.useRef(null);
// Wrap the call in a useEffect to apply the repositioning on first render
React.useEffect(() => {
// Reposition on each rerender
positionMasonry({rootElement: ref.current, gap});
});
// Setup responsive repositionning
React.useEffect(() => {
// Keep track of the device orientation
let orientation = window.screen.orientation.type;
// Use debounce for better performances
const debouncePositionMasonry = debounce(() => {
// Use animation only on window resizing
positionMasonry({rootElement: ref.current, gap, animationDuration: 0.15}),
200
});
const handleResize = () => {
if (!ref.current) return;
// Check if the resizing is a device orientation change
const orientationChange = window.screen.orientation.type !== orientation;
orientation = window.screen.orientation.type;
if (!orientationChange) {
debouncePositionMasonry();
} else {
// Position with no delay & no animation (to avoid mobile browser reflow bug)
positionMasonry({
rootElement: ref.current,
gap,
});
}
}
// Add event listener on resize for responsiveness
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
// Set the ref on the container of the Masonry layout
return <div ref={ref}>{children}</div>;
}
// Just wrap your elements inside the <MasonryLayout>
function App() {
return (
<MasonryLayout gap={30}>
{heights.map((height, i) => (
<div
key={height * i}
className="card"
style={{ width: "260px", height }}
>
{i + 1}
</div>
))}
</MasonryLayout>
);
}