Skip to content

Instantly share code, notes, and snippets.

@gragland
Last active November 23, 2022 16:30
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gragland/ed8cac563f5df71d78f4a1fefa8c5633 to your computer and use it in GitHub Desktop.
Save gragland/ed8cac563f5df71d78f4a1fefa8c5633 to your computer and use it in GitHub Desktop.
import { useState, useEffect } from 'react';
function App() {
const columnCount = useMedia(
// Media queries
['(min-width: 1500px)', '(min-width: 1000px)', '(min-width: 600px)'],
// Column counts (relates to above media queries by array index)
[5, 4, 3],
// Default column count
2
);
// Create array of column heights (start at 0)
let columnHeights = new Array(columnCount).fill(0);
// Create array of arrays that will hold each column's items
let columns = new Array(columnCount).fill().map(() => []);
data.forEach(item => {
// Get index of shortest column
const shortColumnIndex = columnHeights.indexOf(Math.min(...columnHeights));
// Add item
columns[shortColumnIndex].push(item);
// Update height
columnHeights[shortColumnIndex] += item.height;
});
// Render columns and items
return (
<div className="App">
<div className="columns is-mobile">
{columns.map(column => (
<div className="column">
{column.map(item => (
<div
className="image-container"
style={{
// Size image container to aspect ratio of image
paddingTop: (item.height / item.width) * 100 + '%'
}}
>
<img src={item.image} alt="" />
</div>
))}
</div>
))}
</div>
</div>
);
}
// Hook
function useMedia(queries, values, defaultValue) {
// Array containing a media query list for each query
const mediaQueryLists = queries.map(q => window.matchMedia(q));
// Function that gets value based on matching media query
const getValue = () => {
// Get index of first media query that matches
const index = mediaQueryLists.findIndex(mql => mql.matches);
// Return related value or defaultValue if none
return typeof values[index] !== 'undefined' ? values[index] : defaultValue;
};
// State and setter for matched value
const [value, setValue] = useState(getValue);
useEffect(
() => {
// Event listener callback
// Note: By defining getValue outside of useEffect we ensure that it has ...
// ... current values of hook args (as this hook callback is created once on mount).
const handler = () => setValue(getValue);
// Set a listener for each media query with above handler as callback.
mediaQueryLists.forEach(mql => mql.addListener(handler));
// Remove listeners on cleanup
return () => mediaQueryLists.forEach(mql => mql.removeListener(handler));
},
[] // Empty array ensures effect is only run on mount and unmount
);
return value;
}
@nullhook
Copy link

nullhook commented Oct 31, 2020

^ Looks like Safari handles listeners for matchMedia differently. It's addListener on Safari and not addEventListener

Tested on Safari 13.1.3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment