CSS线性渐变拼接,一个完整的渐变容器(div),要拆分成多个渐变容器(div),并且保持渐变效果一致
1 需求
一个有渐变背景的div,需要替换成多个渐变背景div拼接,渐变效果需要保持一致(不通过一个大的div渐变,其他子的div绝对定位其上并且背景透明来解决)
2 分析
主要工作:
- 计算完整div背景线性渐变时的渐变开始线和结束线(假设中心点为几何中心)
- 计算各个独立div背景线性渐变时的渐变开始线和结束线(假设中心点为几何中心)
- 重新计算各个子div的渐变开始线和结束线(百分比)
3 实现
3.1 水平拼接
解释:
- 黑色虚线为各个子div的水平和垂直中心线
- 红色虚线为整个div的水平和垂直中心线
- 绿色虚线为渐变方向上的中心线,和黑色虚线的夹角为各个子div的渐变角
- 紫色虚线为渐变方向上的中心线,和红色虚线的夹角为整个div的渐变角
- 蓝色虚线为渐变开始线和结束线,垂直于渐变方向上的中心线
- 蓝色圆为各个子div独立渐变时的渐变起始中心点,绿色圆为各个子div独立渐变时的渐变结束中心点
- 紫色圆为整个div的渐变起始中心点,红色圆为整个div的渐变结束中心点
- 蓝色紫色双圆为当前子div独立渐变时和其作为整个div整体渐变的一部分时的共同渐变起始中心点,是重合的
- 绿色红色双圆为当前子div独立渐变时和其作为整个div整体渐变的一部分时的共同渐变结束中心点,是重合的
- 蓝色实线段为整个渐变路径,其长度为渐变长度,也就是颜色值后面的参数0-100%
- 绿色实线段为为了作为整体渐变一部分而需要增加的渐变路径
<template>
<div class="container gradient"></div>
<div class="container">
<div
class="container-column"
v-for="(col, index) in itemList"
:key="index"
:style="{
width: col.width + 'px',
height: col.height + 'px',
background: getGradient(col, index)
}"
></div>
</div>
</template>
<script setup lang="ts">
const itemList = ref([
{
width: 100,
height: 400
},
{
width: 200,
height: 400
},
{
width: 300,
height: 400
}
]);
const angle = ref(45);
const getGradient = (item, index) => {
const beforeWidth = itemList.value.slice(0, index).reduce((a, b) => a + b.width, 0);
const afterWidth = itemList.value.slice(index + 1).reduce((a, b) => a + b.width, 0);
const startPosition = `calc(100% - (${beforeWidth} * sin(${angle.value}deg) / (2 * (${item.width / 2} * tan(${angle.value}deg) + ${item.height / 2}) * cos(${angle.value}deg)) + 1) * 100%)`;
const endPosition = `calc((${afterWidth} * sin(${angle.value}deg) / (2 * (${item.width / 2} * tan(${angle.value}deg) + ${item.height / 2}) * cos(${angle.value}deg)) + 1) * 100%)`;
const ret = `linear-gradient(${angle.value}deg,
red ${startPosition},
blue ${endPosition}
)`;
return ret;
};
</script>
<style scoped lang="scss">
.container {
width: 600px;
height: 400px;
display: inline-block;
.container-column {
display: inline-block;
}
}
.gradient {
background: linear-gradient(45deg, red 0%, blue 100%);
}
</style>
效果:
加上边框更清晰点:
3.2 垂直拼接
解释:
- 黑色虚线为各个子div的水平和垂直中心线
- 红色虚线为整个div的水平和垂直中心线
- 绿色虚线为渐变方向上的中心线,和黑色虚线的夹角为各个子div的渐变角
- 紫色虚线为渐变方向上的中心线,和红色虚线的夹角为整个div的渐变角
- 蓝色虚线为渐变开始线和结束线,垂直于渐变方向上的中心线
- 蓝色圆为各个子div独立渐变时的渐变起始中心点,绿色圆为各个子div独立渐变时的渐变结束中心点
- 紫色圆为整个div的渐变起始中心点,红色圆为整个div的渐变结束中心点
- 蓝色紫色双圆为当前子div独立渐变时和其作为整个div整体渐变的一部分时的共同渐变起始中心点,是重合的
- 绿色红色双圆为当前子div独立渐变时和其作为整个div整体渐变的一部分时的共同渐变结束中心点,是重合的
- 蓝色实线段为整个渐变路径,其长度为渐变长度,也就是颜色值后面的参数0-100%
- 绿色实线段为为了作为整体渐变一部分而需要增加的渐变路径
<template>
<div class="container gradient"></div>
<div class="container">
<div
class="container-column"
v-for="(col, index) in itemList"
:key="index"
:style="{
width: col.width + 'px',
height: col.height + 'px',
background: getGradient(col, index)
}"
></div>
</div>
</template>
<script setup lang="ts">
const itemList = ref([
{
width: 400,
height: 100
},
{
width: 400,
height: 200
},
{
width: 400,
height: 300
},
{
width: 400,
height: 300
}
]);
const angle = ref(45);
const getGradient = (item, index) => {
const afterHeight = itemList.value.slice(0, index).reduce((a, b) => a + b.height, 0);
const beforeHeight = itemList.value.slice(index + 1).reduce((a, b) => a + b.height, 0);
const startPosition = `calc(100% - (${beforeHeight} * cos(${angle.value}deg) / (2 * (${item.width / 2} * tan(${angle.value}deg) + ${item.height / 2}) * cos(${angle.value}deg)) + 1) * 100%)`;
const endPosition = `calc((${afterHeight} * cos(${angle.value}deg) / (2 * (${item.width / 2} * tan(${angle.value}deg) + ${item.height / 2}) * cos(${angle.value}deg)) + 1) * 100%)`;
const ret = `linear-gradient(${angle.value}deg,
red ${startPosition},
blue ${endPosition}
)`;
return ret;
};
</script>
<style scoped lang="scss">
.container {
width: 400px;
height: 900px;
display: inline-block;
& + .container {
margin-left: 10px;
}
line-height: 0;
.container-column {
display: inline-block;
}
}
.gradient {
background: linear-gradient(45deg, red 0%, blue 100%);
}
</style>
</style>
效果:
加上边框更清晰点:
3.3 矩阵拼接
图上画线太多,下图画个稍微简明点的
解释:
- 黑色虚线为各个子div的水平和垂直中心线
- 红色虚线为整个div的水平和垂直中心线
- 绿色虚线为渐变方向上的中心线,和黑色虚线的夹角为各个子div的渐变角
- 紫色虚线为渐变方向上的中心线,和红色虚线的夹角为整个div的渐变角
- 蓝色虚线为渐变开始线和结束线,垂直于渐变方向上的中心线
- 蓝色圆为各个子div独立渐变时的渐变起始中心点,绿色圆为各个子div独立渐变时的渐变结束中心点
- 紫色圆为整个div的渐变起始中心点,红色圆为整个div的渐变结束中心点
- 蓝色紫色双圆为当前子div独立渐变时和其作为整个div整体渐变的一部分时的共同渐变起始中心点,是重合的
- 绿色红色双圆为当前子div独立渐变时和其作为整个div整体渐变的一部分时的共同渐变结束中心点,是重合的
- 蓝色实线段为整个渐变路径,其长度为渐变长度,也就是颜色值后面的参数0-100%
- 绿色实线段为为了作为整体渐变一部分而需要增加的渐变路径
<template>
<div class="container gradient"></div>
<div class="container">
<div class="container-row" v-for="(row, rowIndex) in itemList" :key="rowIndex">
<div
class="container-column"
v-for="(col, colIndex) in row"
:key="colIndex"
:style="{
width: col.width + 'px',
height: col.height + 'px',
background: getGradient(col, colIndex, rowIndex)
}"
></div>
</div>
</div>
</template>
<script setup lang="ts">
const itemList = ref([
[
{
width: 100,
height: 100
},
{
width: 300,
height: 100
}
],
[
{
width: 200,
height: 200
},
{
width: 100,
height: 200
},
{
width: 100,
height: 200
}
],
[
{
width: 100,
height: 300
},
{
width: 100,
height: 300
},
{
width: 100,
height: 300
},
{
width: 100,
height: 300
}
],
[
{
width: 400,
height: 300
}
]
]);
const angle = ref(45);
const getGradient = (item, colIndex, rowIndex) => {
const afterHeight = itemList.value.slice(0, rowIndex).reduce((a, b) => a + b[0].height, 0);
const beforeHeight = itemList.value.slice(rowIndex + 1).reduce((a, b) => a + b[0].height, 0);
const beforeWidth = itemList.value[rowIndex].slice(0, colIndex).reduce((a, b) => a + b.width, 0);
const afterWidth = itemList.value[rowIndex].slice(colIndex + 1).reduce((a, b) => a + b.width, 0);
const startPosition = `calc(100% - ((${beforeHeight} * cos(${angle.value}deg) + ${beforeWidth} * sin(${angle.value}deg))/ (2 * (${item.width / 2} * tan(${angle.value}deg) + ${item.height / 2}) * cos(${angle.value}deg)) + 1) * 100%)`;
const endPosition = `calc(((${afterHeight} * cos(${angle.value}deg) + ${afterWidth} * sin(${angle.value}deg)) / (2 * (${item.width / 2} * tan(${angle.value}deg) + ${item.height / 2}) * cos(${angle.value}deg)) + 1) * 100%)`;
const ret = `linear-gradient(${angle.value}deg,
red ${startPosition},
blue ${endPosition}
)`;
return ret;
};
</script>
<style scoped lang="scss">
.container {
width: 400px;
height: 900px;
display: inline-block;
& + .container {
margin-left: 10px;
}
.container-row {
line-height: 0;
.container-column {
display: inline-block;
}
}
}
.gradient {
background: linear-gradient(45deg, red 0%, blue 100%);
}
</style>
效果:
加上边框更清晰点:
4 最后
还可以有更复杂的情况:
- 矩阵中的跨行跨列,大家可自行探索(其实投篮的办法是可以把跨行跨列的单元再进行拆分,使得所有单元都不出现跨行跨列,那么上面第三种矩阵拼接就可以使用了)
- 多个颜色(中间的颜色需要根据当前百分比位置和增加的距离得到最终的百分比位置)
- 非线性渐变的情况(感觉稍微有点复杂)