feat(fc2c-i): usePolyMasonry shortest-column distribution
This commit is contained in:
@@ -0,0 +1,57 @@
|
|||||||
|
// Shortest-column masonry distribution (ported from ImageRepo).
|
||||||
|
//
|
||||||
|
// Predictive relative height = height / width. Using the aspect ratio means
|
||||||
|
// columns are balanced before thumbnails load, so there is no reflow when
|
||||||
|
// images arrive. Missing or zero dimensions fall back to a square (1.0).
|
||||||
|
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
|
||||||
|
function relativeHeight(item) {
|
||||||
|
const w = Number(item.width)
|
||||||
|
const h = Number(item.height)
|
||||||
|
if (!w || !h) return 1
|
||||||
|
return h / w
|
||||||
|
}
|
||||||
|
|
||||||
|
export function distributeIntoColumns(items, columnCount) {
|
||||||
|
const n = Math.max(1, columnCount | 0)
|
||||||
|
const columns = Array.from({ length: n }, () => [])
|
||||||
|
const heights = new Array(n).fill(0)
|
||||||
|
for (const item of items) {
|
||||||
|
let shortest = 0
|
||||||
|
for (let c = 1; c < n; c++) {
|
||||||
|
if (heights[c] < heights[shortest]) shortest = c
|
||||||
|
}
|
||||||
|
columns[shortest].push(item)
|
||||||
|
heights[shortest] += relativeHeight(item)
|
||||||
|
}
|
||||||
|
return columns
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reactive column count from container width. Breakpoints mirror the
|
||||||
|
// gallery grid intent: ~260px target column width, min 1 column.
|
||||||
|
export function columnCountForWidth(width) {
|
||||||
|
if (!width || width < 0) return 1
|
||||||
|
return Math.max(1, Math.floor(width / 260))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePolyMasonry(containerRef) {
|
||||||
|
const columnCount = ref(1)
|
||||||
|
let ro = null
|
||||||
|
|
||||||
|
function measure() {
|
||||||
|
const el = containerRef.value
|
||||||
|
if (el) columnCount.value = columnCountForWidth(el.clientWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
measure()
|
||||||
|
if (typeof ResizeObserver !== 'undefined' && containerRef.value) {
|
||||||
|
ro = new ResizeObserver(measure)
|
||||||
|
ro.observe(containerRef.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onUnmounted(() => ro && ro.disconnect())
|
||||||
|
|
||||||
|
return { columnCount, distribute: distributeIntoColumns }
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { distributeIntoColumns } from '../src/composables/usePolyMasonry.js'
|
||||||
|
|
||||||
|
describe('distributeIntoColumns', () => {
|
||||||
|
it('returns the requested number of columns', () => {
|
||||||
|
const cols = distributeIntoColumns([], 3)
|
||||||
|
expect(cols.length).toBe(3)
|
||||||
|
expect(cols.every(c => c.length === 0)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('places each item in the then-shortest column', () => {
|
||||||
|
// 3 square items (relative height 1 each), 3 columns -> one per column.
|
||||||
|
const items = [
|
||||||
|
{ id: 1, width: 100, height: 100 },
|
||||||
|
{ id: 2, width: 100, height: 100 },
|
||||||
|
{ id: 3, width: 100, height: 100 }
|
||||||
|
]
|
||||||
|
const cols = distributeIntoColumns(items, 3)
|
||||||
|
expect(cols.map(c => c.map(i => i.id))).toEqual([[1], [2], [3]])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('routes a 4th item to a balanced column', () => {
|
||||||
|
const items = [
|
||||||
|
{ id: 1, width: 100, height: 300 }, // tall -> col0 height 3
|
||||||
|
{ id: 2, width: 100, height: 100 }, // col1 height 1
|
||||||
|
{ id: 3, width: 100, height: 100 }, // col2 height 1
|
||||||
|
{ id: 4, width: 100, height: 100 } // shortest is col1 (1) -> col1
|
||||||
|
]
|
||||||
|
const cols = distributeIntoColumns(items, 3)
|
||||||
|
expect(cols[0].map(i => i.id)).toEqual([1])
|
||||||
|
expect(cols[1].map(i => i.id)).toEqual([2, 4])
|
||||||
|
expect(cols[2].map(i => i.id)).toEqual([3])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('treats missing/zero dimensions as a square (height 1)', () => {
|
||||||
|
const cols = distributeIntoColumns([{ id: 9 }], 1)
|
||||||
|
expect(cols[0]).toEqual([{ id: 9 }])
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user