最终实现效果:
实现算法
简单实现一个构造面积函数:
1、需要一个父级盒子,要知道它的宽高;
2、面积块之间的间隙
3、一个宽高比: 这个主要是用于渲染出来的面积块,大部分是满足这个宽高比的。
4、 每次取剩余面积的一条边作为块的宽或者高,最后一块直接等于剩余的面积,
// 外层盒子的一些配置
parentInfo = {
gap: 5, // 间隙
parentW: 599, // 外层宽度
parentH: 168, // 外层高度
squareRatio: 2, // 宽高比
}
// 各个块的初始值,num表示块的大小
arr = [
{ id: 1, num: 2 },
{ id: 2, num: 1 },
{ id: 3, num: 5 },
],
// 传入数据数组,总宽高,生成各自的宽高
getItemsWAndH(parentInfo, arr) {
// 父级盒子信息
const { gap, parentW, parentH, squareRatio } = parentInfo
let remainX = parentW
let remainY = parentH
// 面积块各自占比
const numList = arr.map((i) => i.num)
const resWH = []
arr.forEach((item, index) => {
let obj = { id: item.id }
if (index === arr.length - 1) {
// 最后一个直接等于
obj['width'] = remainX
obj['height'] = remainY
} else {
// 计算剩余面积占比
const remainSum = _.sum(numList.slice(index))
// 当前块占比例
const ratio = item.num / remainSum
if (remainX / remainY < squareRatio) {
// 小于宽高比,取宽度
obj['width'] = remainX
const y = Math.floor(remainY * ratio) // 向下取整
obj['height'] = y
remainY = remainY - y - gap
} else {
// 不小于宽高比,取高度
obj['height'] = remainY
const x = Math.floor(remainX * ratio)
obj['width'] = x
remainX = remainX - x - gap
}
}
// 存储每一项的宽高
resWH.push(obj)
})
// 设置每个项位置
this.items = this.setItemPosition(resWH, this.parentInfo)
},
通过上一个函数就可以计算出每一项的宽高了,接下来就是满满当当的塞到父级盒子中,一开始直接用的flex布局
// flex布局,是不行的,
.rec-area-map {
display: flex;
flex-flow: column wrap;
gap: 5px;
}
但是flex布局无法适应下面这种情况:
所以改用js构造整个面积图内的各个块的位置。主要是利用absolute
, top
和left
来定义每个块的位置
setItemPosition(list, parentInfo) {
// 行列间隙
const divVGap = parentInfo.gap || 5
const divHGap = parentInfo.gap || 5
// 父级盒子宽高
let boxWidth = parentInfo.parentW
let boxHeight = parentInfo.parentH
// 计算每一个项放入后的left和top位置
let left = 0
let top = 0
list.forEach((item, i) => {
const w = item.width
const h = item.height
item.left = left
item.top = top
// 宽度相等时
if(w === boxWidth) {
const localH = h + divHGap
top += localH
boxHeight -= localH
}
// 高度相等时
if(h === boxHeight) {
const localW = w + divVGap
left += localW
boxWidth -= localW
}
})
return list
},
完整代码
<template>
<div class="rec-area-map" :style="parentStyle">
<div
v-for="(item, index) in items"
:key="item.id"
:style="{
width: item.width + 'px',
height: item.height + 'px',
background: colors[index],
top: item.top + 'px',
left: item.left + 'px',
position: 'absolute'
}"
>
<div class="grid-content">
<slot :info="{...item, index}">{{item.id}}</slot>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
colors: {
type: Array,
default: () => [
'#E6F5FF',
'#ECF2FF',
'#E4FBF4',
'#dfe7d7',
'#cfe3ef',
'#f1e0dc',
'#e3c7c5',
'#bbd69d',
],
},
config: {
type: Object,
default: () => ({}),
},
ratioList: {
type: Array,
default: () => [
{ id: 1, num: 2 },
{ id: 2, num: 1 },
{ id: 3, num: 1 },
],
},
},
data() {
return {
items: [],
}
},
computed: {
parentStyle() {
return {
width: this.parentInfo.parentW + 'px',
height: this.parentInfo.parentH + 'px',
gap: this.parentInfo.gap + 'px',
}
},
parentInfo() {
return {
gap: 5, // 间隙
parentW: 599, // 外层宽度
parentH: 168, // 外层高度
squareRatio: 2, // 宽高比
...this.config, // 外部传入,覆盖上面的
}
},
},
methods: {
// 传入数据数组,总宽高,生成各自的宽高
getItemsWAndH(parentInfo, arr) {
// 父级盒子信息
const { gap, parentW, parentH, squareRatio } = parentInfo
let remainX = parentW
let remainY = parentH
// 各个块的面积值
const numList = arr.map((i) => i.num)
const resWH = []
arr.forEach((item, index) => {
let obj = { id: item.id }
if (index === arr.length - 1) {
// 最后一个直接等于
obj['width'] = remainX
obj['height'] = remainY
} else {
// 计算剩余面积占比
const remainSum = _.sum(numList.slice(index))
const ratio = item.num / remainSum
console.log(ratio, squareRatio, remainX, remainY, remainX / remainY)
if (remainX / remainY < squareRatio) {
// 小于宽高比,取宽度
obj['width'] = remainX
const y = Math.floor(remainY * ratio) // 向下取整
obj['height'] = y
remainY = remainY - y - gap
} else {
// 不小于宽高比,取高度
obj['height'] = remainY
const x = Math.floor(remainX * ratio)
obj['width'] = x
remainX = remainX - x - gap
}
}
resWH.push(obj)
})
this.items = this.setItemPosition(resWH, this.parentInfo)
},
setItemPosition(list, parentInfo) {
// 行列间隙
const divVGap = parentInfo.gap || 5
const divHGap = parentInfo.gap || 5
// 父级盒子宽高
let boxWidth = parentInfo.parentW
let boxHeight = parentInfo.parentH
// 计算每一个项放入后的left和top位置
let left = 0
let top = 0
list.forEach((item, i) => {
const w = item.width
const h = item.height
item.left = left
item.top = top
// 宽度相等时
if(w === boxWidth) {
const localH = h + divHGap
top += localH
boxHeight -= localH
}
// 高度相等时
if(h === boxHeight) {
const localW = w + divVGap
left += localW
boxWidth -= localW
}
})
return list
},
},
watch: {
ratioList: {
handler() {
this.getItemsWAndH(this.parentInfo, this.ratioList)
},
deep: true,
immediate: true,
},
},
}
</script>
<style lang="scss" scoped>
.rec-area-map {
position: relative;
}
.grid-content {
height: 100%;
width: 100%;
}
</style>
待改进
1、最小面积块
2、现有算法无法实现瀑布流那种,仅适用于小区域使用