Skip to content

Instantly share code, notes, and snippets.

@LinusBorg LinusBorg/Masonry.vue
Last active Jun 24, 2019

Embed
What would you like to do?
Example of proposed Vue setup method

Code is using features proposed in RFC #42 of Vue.js, see: https://github.com/vuejs/rfcs/pull/42

This is based on a React implementation, see: https://codesandbox.io/s/26mjowzpr?from-embed

It was a challenge from a comment on dev.to: https://dev.to/danielelkington/comment/c85f

Notes:

  1. Of course this is not operational
  2. Because of 1., it's sure to have some bug where I forgot an import or something, or broke something else
  3. This aimed to stay similar to the React example.
  4. There's no vue-spring and I have no idea how react.spring works, so I bailed for a simple <transition-group>
  5. All of these features and the syntax are of course subject to change as RFC #42 progresses
export default [
{
css:
'url(https://images.pexels.com/photos/416430/pexels-photo-416430.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260)',
height: 150,
id: 1,
},
{
css:
'url(https://images.pexels.com/photos/1103970/pexels-photo-1103970.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260)',
height: 300,
id: 2,
},
//...
//...
]
<template>
<div ref="container">
<transition-group name="flip">
<div
v-for="item in gridItems"
:key="item.key"
:style="{ backgroundImage: item.css, height: item.height }"
/>
</transition-group>
</div>
</template>
<script>
/* eslint-disable */
import { useMedia } from './useMedia.js'
import { useMeasure } from './useMeasure'
import shuffle from 'lodash/shuffle'
import data from './data'
import {
value,
onMounted,
onDestroy,
} from 'vue'
function shuffleData() {
// using a value() so we can replace the reference
// Note: vaulue() is still concidered to be too unintuitive
// and we look into possible optimizations
const items = value(shuffle(data))
let id
onMounted(() => {
id = setInterval(() => items.value = shuffle(data), 2000)
})
onDestroy(() => { id() })
return items
}
export default {
setup(props, { refs }) {
const columns = useMedia(
['(min-width: 1500px)', '(min-width: 1000px)', '(min-width: 600px)'],
[5, 4, 3],
2
)
const rect = useMeasure(refs, 'container')
const items = shuffleData()
const gridItems = computed(() => {
const heights = new Array(columns.value).fill(0)
const { width } = rect.width
return items.value.map((child, i) => {
const column = heights.indexOf(Math.min(...heights)) // Basic masonry-grid placing, puts tile into the smallest column using Math.min
const xy = [(width / columns) * column, (heights[column] += child.height / 2) - child.height / 2] // X = container width / number of columns * column index, Y = it's just the height of the current column
return { ...child, xy, width: width / columns, height: child.height / 2 }
})
})
return {
gridItems
}
}
}
</script>
<style lang="scss">
.flip-list-move {
transition: transform 1s;
}
</style>
import ResizeObserver from 'resize-observer-polyfill'
import { state, onDestroy, onMounted } from 'vue'
export function useMeasure(refs, name) {
const rect = state({})
const ro = new ResizeObserver(([entry]) =>
Object.assign(rect, entry.contentRec)
)
onMounted(() => ro.observe(refs[name]))
onDestroy(() => ro.disconnect)
return rect
}
import { value, onDestroy } from 'vue'
export function useMedia(queries, values, defaultValue) {
const getMatch = () =>
values[queries.findIndex(q => matchMedia(q).matches)] || defaultValue
const match = value(getMatch())
function handler() {
match.value = getMatch()
}
window.addEventListener('resize', handler)
onDestroy(() => {
window.removeEventListener('resize', handler)
})
return match
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.