10000 个随机方块生成(方块颜色回形渐变)
最近部门搞一个实战活动,旨在提高个人开发思考能力。利用自己业余时间,完成了一些基础开发。 最终实现效果:
题目:
1 页面自动布局 M 个正方形方块,M 的初始值为 3,且 M >= 3;每行展示正方形方块数 N 自行设置。这些方块的颜色用 RGB 格式描述:{ Red:X, Green:Y, Blue:Z } ,其中{ X,Y,Z }为程序编码实现的 0 到 255 之间的随机整数值。
2 同时,在页面顶端增加实现一个用于“修改正方形方块数 M”的【M 值输入框】和一个用于“修改一行正方形方块数 N”的【N 值输入框】和一个确认修改 M 的【确认】按钮。在用户输入新的 M 值,并点击【确认】之后,页面自动布局新的 M 个正方形方块,并为每个方块重新赋予新的随机 RGB 颜色值。
3 修改 M 值或 N 值之后,页面能在 1 秒内加载出来。并且,页面上方块颜色要能实现渐变效果。
4 能根据不同屏幕宽度自适应布局方块、并且能动态保持渐变效果。
试题分析
实现 M 和 N 的输入框比较简单,主要是根据 M 值自动生成任意个方块,而且方块的颜色是需要有规律的,需要实现整体渐变效果。
要想颜色实现渐变,主要要清楚颜色的规律。RGB 颜色是三基色(红,绿,蓝)叠加相成的颜色,一个基色对应 256 级亮度。三基色叠加能组合出约约 1678 万种色彩,即 256×256×256 = 16777216。当其中一个基色递增或者递减,另外两个基色不变时,就能形成一种渐变效果。
1、简单实现页面布局及效果
实现页面方块自适应,也就是无论屏幕宽度多大,屏幕中一行的方块数是固定的。即单个方块的宽度是变化的,在这里可以使用百分比来为方块设置宽度,及一个方块的宽度是(100 / N)%,并且设置方块的父容器弹性布局,并且自动换行,最终就能实现任意个方块自适应布局。
宽度的值固定了,高度也需要做一些处理。这里有两种方案:
- 高度可以使用 aspect-ratio 属性,值设置为 1 即可
- 使用 padding
本文使用的是第二种方法,利用 padding 撑开盒子,原理:padding 设置百分比的是基于盒子的宽度来计算的。
基础布局代码实现
<template>
<div style="margin-top:50px">
<el-form inline
size="small">
<el-form-item label="总数量">
<el-input-number :min="3"
v-model="totalNum"
placeholder="请输入总数量"></el-input-number>
</el-form-item>
<el-form-item label="每行个数">
<el-input-number :min="1"
v-model="columnNum"
placeholder="请输入每行个数"></el-input-number>
</el-form-item>
<el-form-item label="颜色变化大小">
<el-input-number :min="1"
v-model="stepNum"
placeholder="颜色变化大小"></el-input-number>
</el-form-item>
<el-form-item label="">
<el-button type="primary"
@click="handSetTotalNum()">确定</el-button>
</el-form-item>
</el-form>
<div class="content">
<div class="list-item"
v-for="item in squareS"
:key="item.value"
:style="{width: item.width,paddingTop:item.paddingTop}">
</div>
</div>
</div>
</template>
<script>
export default {
name: 'AboutView',
data() {
return {
squareS: [], // 所有矩形集合
totalNum: 100, // 所有方块数
columnNum: 10, // 一行方块数
}
},
created() {
this.handSetTotalNum()
},
methods: {
handSetTotalNum() {
const num = this.totalNum
const { totalNum, columnNum } = this
const width = `${100 / columnNum}%`
// 本身paddingTop和width一样即可生成一个正方形,考虑到矩形块太多,每个矩形块之间
const paddingTop = `calc(${width} - 2px)`
const data = []
for (let i = 0; i < num; i++) {
data.push({
value: i,
width,
paddingTop,
})
this.squareS = data
}
}
},
}
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
.el-form {
position: fixed;
top: 8px;
}
.content {
max-height: calc(100vh - 50px);
overflow: auto;
margin-top: 50px;
display: flex;
flex-wrap: wrap;
}
.list-item {
height: 0;
border: 1px solid #fff;
box-sizing: border-box;
}
</style>
<style>
body {
margin: 0;
}
</style>
方块颜色回形渐变
原理就是 M 个矩形组合成的图形,本质上是由一个个矩形组合而成。当前图形是由多个矩形组合而成,想要实现这种回形渐变效果,那么一个回形颜色的就应该是想关联的。即可以把一个回形当做一组。我们在这一组中实现颜色渐变即可。
回形之间的关系
根据方块总数和图形的方块列数,我们可以计算出当前图形总共有多少行。根据列数和行数,可以得到以下规律:
- 最外层的回形,是由第一列,第一行,最后一行,最后一列,组合而成
- 第二个回形就是第二列,第二行,倒数第二列,倒数第二行
- ...
图形是在浏览器视图上,我们可以把每个矩形可以当做一个坐标点,以浏览器视图左上角第一个矩形为原点,坐标为(0,0)。横向为 x 轴,纵向为 y 轴。那么绘制的每个矩形都有自己的坐标。根据上面得到的规律,我们可以计算出每个回形的坐标点。一个回形对应一组渐变色,最终多组图形整体形成回形渐变。
代码实现
图形组成的矩形的个数和图形的列数是变化的,根据规律可以得到回形的坐标点。每个回形矩形是有两条长和两条宽组成,观察图形的规律,每个回形的长有如下规律
- 第一层回形:由图形列数个矩形组成
- 第二层回形: 图形列数 - 2
- 第三层回形: 图形列数 - 4
- ...
- 依此递减,直到为 0
每个回形的宽也有着同样的规律,宽由图形的行数决定。这里需要注意一下,回形的长和宽的交界处会公用一个矩形,这里需要考虑到,这个矩形算入到回形长上面后,宽就得在回形行数的基础上减 2 再递减。
- 第一层回形:由图形行数 - 2
- 第二层回形: 图形行数 - 4
- 第三层回形: 图形行数 - 6
- ...
- 依此递减,直到为 0
已知图形行数和列数,回形的个数也可以确定。回形的个数是行数和列数的中的最小值除以 2,会存在不能整除的情况,相当与是只有一条线组成。其实我们在处理的时候,不用关心回形的个数,因为无论取行数还是列数,在递减遍历的时候,其中只要有一个为 0 我们不再处理即可。
定义一些初始变量
let { columnNum, totalNum, rowNum } = this;
let initX = columnNum;
let initY = rowNum - 2;
const arr = [];
遍历设置多个回形颜色数组,即一个二维数组。获取一个当前回形矩形个数的颜色数组,以回形的左上角为起点,顺时针方向回形渐变。根据规律,获取当前回形的每个矩形的颜色。
for (let i = 0; i < columnNum / 2; i++) {
// 传入回形的矩形个数,获取颜色连续数组,当前回形的颜色集合
const colorArr = this.handRangeColor(initX * 2 + initY * 2);
// 回形的对应边
const side1 = [];
const side2 = [];
const side3 = [];
const side4 = [];
// 获取两条长的各个点坐及集颜色
for (let j = 0; j < initX; j++) {
const obj1 = {
y: i,
x: j + i,
color: colorArr[j],
};
const obj3 = {
y: rowNum - i - 1,
x: j + i,
color: colorArr[2 * initX + initY - j - 1],
};
side1.push(obj1);
side3.push(obj3);
}
for (let j = 0; j < initY; j++) {
const obj2 = {
y: j + i + 1,
x: i,
color: colorArr[colorArr.length - j - 1],
};
const obj4 = {
y: j + 1 + i,
x: columnNum - i - 1,
color: colorArr[initX + j],
};
side2.push(obj2);
side4.push(obj4);
}
initY = initY - 2;
initX = initX - 2;
if (initY < 0) {
initY = 0;
}
if (initX < 0) {
initX = 0;
}
const square = [side1, side2, side3, side4];
arr.push(square);
}
获取颜色数组
handRangeColor(num) {
const radom1 = Math.floor(Math.random() * 3)
const radom2 = Math.floor(Math.random() * 255)
const colorArr = []
for (let i = 0; i < num; i++) {
let color = []
for (let j = 0; j < 3; j++) {
if (j === radom1) {
const value = (radom2 + 3 * i) % 255
color[j] = value
} else {
color[j] = radom2
}
}
colorArr.push(`rgb(${color.join(', ')})`)
}
return colorArr
},
将回形二维数组铺平,获得所有的矩形的特性集合。为了提高获取某个矩形的特性,可以将该矩形特性集合做 map 处理,通过对应的key快速的查找对应的矩形。
const map = new Map();
// 获取当前图形回形的矩形集合,值为一个二维数组
const colorArr = this.handRow();
const flatArr = colorArr.flat(2);
for (let i = 0; i < flatArr.length; i++) {
const { x, y, color } = flatArr[i];
map.set(`${x}x,${y}y`, color);
}
遍历所有矩形集合,将当前矩形与获取的矩形map对应起来
cons data = []
for (let i = 0; i < num; i++) {
const y = Math.floor(i / columnNum)
const x = i % columnNum
const color = map.get(`${x}x,${y}y`)
data.push({
value: i,
bgColor: color,
width,
paddingTop,
})
}
自此,就完成了一个回形渐变图形的绘制。
完整代码
<template>
<div style="margin-top:50px">
<el-form :model="configData"
inline
size="small">
<el-form-item label="总数量">
<el-input-number :min="3"
v-model="totalNum"
placeholder="请输入总数量"></el-input-number>
</el-form-item>
<el-form-item label="每行个数">
<el-input-number :min="1"
v-model="columnNum"
placeholder="请输入每行个数"></el-input-number>
</el-form-item>
<el-form-item label="颜色变化大小">
<el-input-number :min="1"
v-model="stepNum"
placeholder="颜色变化大小"></el-input-number>
</el-form-item>
<el-form-item label="">
<el-button type="primary"
@click="handSetTotalNum()">确定</el-button>
</el-form-item>
</el-form>
<div class="content">
<div class="list-item"
v-for="item in squareS"
:key="item.value"
:style="{background: item.bgColor ,width: item.width,paddingTop:item.paddingTop}">
</div>
</div>
</div>
</template>
<script>
export default {
name: 'AboutView',
data() {
return {
squareS: [],
totalNum: 100,
columnNum: 10,
rowNum: 40,
stepNum: 3,
configData: {}
}
},
created() {
this.handSetTotalNum()
},
methods: {
handSetColor() {
const radomNum = Math.floor(Math.random() * 256)
return radomNum
},
handSetTotalNum() {
const num = this.totalNum
const { totalNum, columnNum } = this
this.rowNum = Math.ceil(totalNum / columnNum)
const colorArr = this.handRow()
const map = new Map()
const flatArr = colorArr.flat(2)
for (let i = 0; i < flatArr.length; i++) {
const { x, y, color } = flatArr[i]
map.set(`${x}x,${y}y`, color)
}
const data = []
const width = `${100 / columnNum}%`
const paddingTop = `calc(${width} - 2px)`
for (let i = 0; i < num; i++) {
const y = Math.floor(i / columnNum)
const x = i % columnNum
const color = map.get(`${x}x,${y}y`)
data.push({
value: i,
bgColor: color,
width,
paddingTop,
})
}
this.squareS = data
},
// 获取矩形四边点数组
handRow() {
let { columnNum, totalNum, rowNum } = this
let initX = columnNum
let initY = rowNum - 2
const arr = []
for (let i = 0; i < columnNum / 2; i++) {
const colorArr = this.handRangeColor(initX * 2 + initY * 2)
const side1 = []
const side2 = []
const side3 = []
const side4 = []
for (let j = 0; j < initX; j++) {
const obj1 = {
y: i,
x: j + i,
color: colorArr[j]
}
const obj3 = {
y: rowNum - i - 1,
x: j + i,
color: colorArr[2 * initX + initY - j - 1]
}
side1.push(obj1)
side3.push(obj3)
}
for (let j = 0; j < initY; j++) {
const obj2 = {
y: j + i + 1,
x: i,
color: colorArr[colorArr.length - j - 1]
}
const obj4 = {
y: j + 1 + i,
x: columnNum - i - 1,
color: colorArr[initX + j]
}
side2.push(obj2)
side4.push(obj4)
}
initY = initY - 2
initX = initX - 2
if (initY < 0) {
initY = 0
}
if (initX < 0) {
initX = 0
}
const square = [
side1,
side2,
side3,
side4,
]
arr.push(square)
}
return arr
},
handRangeColor(num) {
const radom1 = Math.floor(Math.random() * 3)
const radom2 = Math.floor(Math.random() * 255)
const colorArr = []
for (let i = 0; i < num; i++) {
let color = []
for (let j = 0; j < 3; j++) {
if (j === radom1) {
const value = (radom2 + 3 * i) % 255
color[j] = value
} else {
color[j] = radom2
}
}
colorArr.push(`rgb(${color.join(', ')})`)
}
return colorArr
},
handRadomStep() {
const stepNum = Math.ceil(Math.random() * 20)
this.stepNum = stepNum
this.handSetTotalNum()
}
},
}
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
.el-form {
position: fixed;
top: 8px;
}
.content {
max-height: calc(100vh - 50px);
overflow: auto;
margin-top: 50px;
display: flex;
flex-wrap: wrap;
}
.list-item {
height: 0;
border: 1px solid #fff;
box-sizing: border-box;
}
</style>
<style>
body {
margin: 0;
}
</style>
结语
本文主要是实现了矩形的回形图形渐变,用到了一些坐标相关的基础知识。代码还有一些不足之处,在生成10000个左右的方块的情况下,页面会出现一些卡顿情况。如果想要实现更多的矩形,应该是需要对代码的写法上做一些优化,或者使用其他思路。
文章可能写的比较乱,会有一些问题,还请大佬们指正,谢谢。