网格中的按钮,间隙中有箭头
buttons in a grid with arrows in gaps
我有显示在 css 网格上的按钮。我想把箭头放在像流程图一样的空白处。如果不可能使用 css grid 可以考虑使用另一种技术来实现的东西。这是 stackblitz,它将显示我的实现,中间没有箭头。 https://stackblitz.com/edit/angular-wipq2r-miu8fl?file=src%2Fapp%2Fproduct-list%2Fproduct-list.component.html
这里有一个例子:
我会根据箭头需要指向的方向(上、下、左、右)创建可应用于每个“步骤”的 classes,然后使用 ::after
用于在每个 class 上创建箭头元素的伪选择器,根据需要设置样式。
见下文。
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
grid-gap: 20px;
}
.step {
background-color: blue;
color: white;
padding: 10px;
position: relative;
}
.arrow-right::after {
color: black;
content: '→';
font-size: 20px;
position: absolute;
left: 100%;
top: 50%;
transform: translateY(-50%);
}
.arrow-down::after {
color: black;
content: '↓';
font-size: 16px;
position: absolute;
left: 50%;
top: 100%;
transform: translateX(-50%);
}
.arrow-up::after {
color: black;
content: '↑';
font-size: 16px;
position: absolute;
left: 50%;
bottom: 100%;
transform: translateX(-50%);
}
.arrow-left::after {
color: black;
content: '←';
font-size: 20px;
position: absolute;
right: 100%;
top: 50%;
transform: translateY(-50%);
}
<div class="grid">
<div class="step arrow-right">Step X</div>
<div class="step arrow-down">Step X</div>
<div class="step">Step X</div>
<div class="step">Step X</div>
<div class="step arrow-down">Step X</div>
<div class="step">Step X</div>
<div class="step arrow-up">Step X</div>
<div class="step arrow-left">Step X</div>
<div class="step">Step X</div>
</div>
另一种方法是使用 SVG
**更新** 我忘记在计算路径时添加 window.scrollX 和 window.scrollY (只是在代码和 stactblitz 中更正)
更新2当我们从element.getBoundingClientRect()计算我们需要susbstrat的位置时,"wrapper的getBoundingClientRect().top和getBoundingClientRect().left “
这个想法被包装起来div
<div #wrapper class="wrapper">
<svg [attr.width]="size.width" [attr.height]="size.height"
xmlns="http://www.w3.org/2000/svg">
<path *ngFor="let path of paths" [attr.d]="path" />
</svg>
<div #bt *ngFor="let item of procDesc; let i = index" class="step">
..your buttons..
</div>
</div>
您使用 viewChild 和 ViewChildren 获取元素并声明一个“路径”数组和一个具有包装器大小的对象
@ViewChildren('bt') items:QueryList<ElementRef>
@ViewChild('wrapper') wrapper:ElementRef
paths:string[]=[]
size={width:0,height:0}
然后,当您调整大小时(我在 the stackblitz 中使用 fromEvent rxjs 运算符)
ngOnInit()
{
this.subscription=fromEvent(window,'resize').pipe(
startWith(null),
debounceTime(200)
).subscribe(_=>{
setTimeout(()=>{
this.paths=this.createPath()
const rect=this.wrapper.nativeElement.getBoundingClientRect()
this.size={width:rect.width,height:rect.height}
})
})
}
ngOnDestroy(){
this.subscription.unsubscribe()
}
函数createPath就像
createPath()
{
const path:string[]=[]
const add=.5; //if stroke-width is even use add=.5 else use add=0
//get the position of "wrapper"
const wrapper=this.wrapper.nativeElement.getBoundingClientRect()
this.items.forEach((x,i)=>{
if (i)
{
/* replace this lines
const ini=this.items.find((_,index)=>index==i-1)
.nativeElement.getBoundingClientRect()
const fin=x.nativeElement.getBoundingClientRect()
*/
//by
const _ini=this.items.find((_,index)=>index==i-1)
.nativeElement.getBoundingClientRect()
const _fin=x.nativeElement.getBoundingClientRect()
const ini={width:_ini.width,height:_ini.height,
left:_ini.left+window.scrollX,top:_ini.top+window.scrollY}
const fin={width:_fin.width,height:_fin.height,
left:_fin.left+window.scrollX,top:_fin.top+window.scrollY}
const _ini=this.items.find((_,index)=>index==i-1)
.nativeElement.getBoundingClientRect()
const _fin=x.nativeElement.getBoundingClientRect()
const ini={width:_ini.width,height:_ini.height,
left:_ini.left-wrapper.left,top:_ini.top-wrapper.top}
const fin={width:_fin.width,height:_fin.height,
left:_fin.left-wrapper.left,top:_fin.top-wrapper.top}
if (ini.top==fin.top)
{
path.push(`M${ini.left+ini.width+add} ${ini.top+ini.height/2+add}
H${fin.left-add}
M${fin.left-7-add} ${fin.top+fin.height/2-4-add}
L${fin.left-add} ${fin.top+fin.height/2+add}
M${fin.left-7-add} ${fin.top+fin.height/2+4+add}
L${fin.left-add} ${fin.top+fin.height/2+add}`)
}
else
{
const step=6; //(fin.top-ini.top-ini.height)/2
path.push(`M${ini.left+ini.width/2+add} ${ini.top+ini.height+add}
V${ini.top+ini.height+step+add}
H${fin.left+fin.width/2+add}
V${fin.top-add}
M${fin.left+fin.width/2+4+add} ${fin.top-7-add}
L${fin.left+fin.width/2+add} ${fin.top-add}
M${fin.left+fin.width/2-4-add} ${fin.top-7-add}
L${fin.left+fin.width/2+add} ${fin.top-add}`
)
}
}
})
return path
}
由于箭头只是视觉线索,我们可以使用伪元素来描绘它们。
您可以 'draw' 通过在背景颜色为黑色的项目上放置伪后元素并使用 clip-path 剪切它来 'draw' 线条和箭头。
我们需要两个 clip-path,一个用于水平 lines/arrows,一个用于从每行的最后一项到下一行的第一项。
此代码段使用一些 CSS 变量的设置,以便更轻松地进行更改以适应特定用例。
结果如下:
* {
margin: 0;
padding;
0;
box-sizing: border-box;
}
.container {
--gap: 10vmin;
/* set this to what you want the gap to be - absolute units */
display: grid;
gap: var(--gap);
grid-template-columns: 1fr 1fr 1fr;
width: 100vw;
/* set this to whatever you want */
padding: var(--gap);
}
.container>* {
border: solid 1px black;
width: 100%;
--ar: 3 / 1;
/* set this to what you want the aspect ratio of a button to be */
aspect-ratio: var(--ar);
position: relative;
}
.container>*::after {
content: '';
width: var(--gap);
--arrow: 20px;
/* set this to the height of the arrowhead */
--ha: calc(var(--arrow) / 2);
/* half the height of an arrowhead */
--line: 2px;
/* set this to the width (height) of the lines */
--hl: calc(var(--line) / 2);
/* half the height of a line */
height: var(--arrow);
background-color: black;
clip-path: polygon(0 calc(var(--ha) - var(--hl)), calc(100% - var(--ha)) calc(var(--ha) - var(--hl)), calc(100% - var(--ha)) 0, 100% 50%, calc(100% - var(--ha)) 100%, calc(100% - var(--ha)) calc(var(--ha) + var(--hl)), 0 calc(var(--ha) + var(--hl)));
position: absolute;
top: 50%;
transform: translateY(-50%);
right: calc(-1 * var(--gap));
}
.container>*:nth-child(3n)::after {
width: calc(200% + (2 * var(--gap)) + var(--line));
height: var(--gap);
transform: translateY(0);
top: 100%;
right: calc(50% - (var(--hl)));
clip-path: polygon( 100% 0, 100% calc(50% + var(--hl)), calc(var(--ha) + var(--hl)) calc(50% + var(--hl)), calc(var(--ha) + var(--hl)) calc(100% - var(--ha)), var(--arrow) calc(100% - var(--ha)), var(--ha) 100%, 0 calc(100% - var(--ha)), calc(var(--ha) - var(--hl)) calc(100% - var(--ha)), calc(var(--ha) - var(--hl)) calc(50% - var(--hl)), calc(100% - var(--line)) calc(50% - var(--hl)), calc(100% - var(--line)) 0);
}
.container>*:last-child::after {
display: none;
}
<div class="container">
<button></button><button></button><button></button><button></button><button></button><button></button><button></button><button></button><button></button>
</div>
我有显示在 css 网格上的按钮。我想把箭头放在像流程图一样的空白处。如果不可能使用 css grid 可以考虑使用另一种技术来实现的东西。这是 stackblitz,它将显示我的实现,中间没有箭头。 https://stackblitz.com/edit/angular-wipq2r-miu8fl?file=src%2Fapp%2Fproduct-list%2Fproduct-list.component.html
这里有一个例子:
我会根据箭头需要指向的方向(上、下、左、右)创建可应用于每个“步骤”的 classes,然后使用 ::after
用于在每个 class 上创建箭头元素的伪选择器,根据需要设置样式。
见下文。
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
grid-gap: 20px;
}
.step {
background-color: blue;
color: white;
padding: 10px;
position: relative;
}
.arrow-right::after {
color: black;
content: '→';
font-size: 20px;
position: absolute;
left: 100%;
top: 50%;
transform: translateY(-50%);
}
.arrow-down::after {
color: black;
content: '↓';
font-size: 16px;
position: absolute;
left: 50%;
top: 100%;
transform: translateX(-50%);
}
.arrow-up::after {
color: black;
content: '↑';
font-size: 16px;
position: absolute;
left: 50%;
bottom: 100%;
transform: translateX(-50%);
}
.arrow-left::after {
color: black;
content: '←';
font-size: 20px;
position: absolute;
right: 100%;
top: 50%;
transform: translateY(-50%);
}
<div class="grid">
<div class="step arrow-right">Step X</div>
<div class="step arrow-down">Step X</div>
<div class="step">Step X</div>
<div class="step">Step X</div>
<div class="step arrow-down">Step X</div>
<div class="step">Step X</div>
<div class="step arrow-up">Step X</div>
<div class="step arrow-left">Step X</div>
<div class="step">Step X</div>
</div>
另一种方法是使用 SVG
更新2当我们从element.getBoundingClientRect()计算我们需要susbstrat的位置时,"wrapper的getBoundingClientRect().top和getBoundingClientRect().left “
这个想法被包装起来div
<div #wrapper class="wrapper">
<svg [attr.width]="size.width" [attr.height]="size.height"
xmlns="http://www.w3.org/2000/svg">
<path *ngFor="let path of paths" [attr.d]="path" />
</svg>
<div #bt *ngFor="let item of procDesc; let i = index" class="step">
..your buttons..
</div>
</div>
您使用 viewChild 和 ViewChildren 获取元素并声明一个“路径”数组和一个具有包装器大小的对象
@ViewChildren('bt') items:QueryList<ElementRef>
@ViewChild('wrapper') wrapper:ElementRef
paths:string[]=[]
size={width:0,height:0}
然后,当您调整大小时(我在 the stackblitz 中使用 fromEvent rxjs 运算符)
ngOnInit()
{
this.subscription=fromEvent(window,'resize').pipe(
startWith(null),
debounceTime(200)
).subscribe(_=>{
setTimeout(()=>{
this.paths=this.createPath()
const rect=this.wrapper.nativeElement.getBoundingClientRect()
this.size={width:rect.width,height:rect.height}
})
})
}
ngOnDestroy(){
this.subscription.unsubscribe()
}
函数createPath就像
createPath()
{
const path:string[]=[]
const add=.5; //if stroke-width is even use add=.5 else use add=0
//get the position of "wrapper"
const wrapper=this.wrapper.nativeElement.getBoundingClientRect()
this.items.forEach((x,i)=>{
if (i)
{
/* replace this lines
const ini=this.items.find((_,index)=>index==i-1)
.nativeElement.getBoundingClientRect()
const fin=x.nativeElement.getBoundingClientRect()
*/
//by
const _ini=this.items.find((_,index)=>index==i-1)
.nativeElement.getBoundingClientRect()
const _fin=x.nativeElement.getBoundingClientRect()
const ini={width:_ini.width,height:_ini.height,
left:_ini.left+window.scrollX,top:_ini.top+window.scrollY}
const fin={width:_fin.width,height:_fin.height,
left:_fin.left+window.scrollX,top:_fin.top+window.scrollY}
const _ini=this.items.find((_,index)=>index==i-1)
.nativeElement.getBoundingClientRect()
const _fin=x.nativeElement.getBoundingClientRect()
const ini={width:_ini.width,height:_ini.height,
left:_ini.left-wrapper.left,top:_ini.top-wrapper.top}
const fin={width:_fin.width,height:_fin.height,
left:_fin.left-wrapper.left,top:_fin.top-wrapper.top}
if (ini.top==fin.top)
{
path.push(`M${ini.left+ini.width+add} ${ini.top+ini.height/2+add}
H${fin.left-add}
M${fin.left-7-add} ${fin.top+fin.height/2-4-add}
L${fin.left-add} ${fin.top+fin.height/2+add}
M${fin.left-7-add} ${fin.top+fin.height/2+4+add}
L${fin.left-add} ${fin.top+fin.height/2+add}`)
}
else
{
const step=6; //(fin.top-ini.top-ini.height)/2
path.push(`M${ini.left+ini.width/2+add} ${ini.top+ini.height+add}
V${ini.top+ini.height+step+add}
H${fin.left+fin.width/2+add}
V${fin.top-add}
M${fin.left+fin.width/2+4+add} ${fin.top-7-add}
L${fin.left+fin.width/2+add} ${fin.top-add}
M${fin.left+fin.width/2-4-add} ${fin.top-7-add}
L${fin.left+fin.width/2+add} ${fin.top-add}`
)
}
}
})
return path
}
由于箭头只是视觉线索,我们可以使用伪元素来描绘它们。
您可以 'draw' 通过在背景颜色为黑色的项目上放置伪后元素并使用 clip-path 剪切它来 'draw' 线条和箭头。
我们需要两个 clip-path,一个用于水平 lines/arrows,一个用于从每行的最后一项到下一行的第一项。
此代码段使用一些 CSS 变量的设置,以便更轻松地进行更改以适应特定用例。
结果如下:
* {
margin: 0;
padding;
0;
box-sizing: border-box;
}
.container {
--gap: 10vmin;
/* set this to what you want the gap to be - absolute units */
display: grid;
gap: var(--gap);
grid-template-columns: 1fr 1fr 1fr;
width: 100vw;
/* set this to whatever you want */
padding: var(--gap);
}
.container>* {
border: solid 1px black;
width: 100%;
--ar: 3 / 1;
/* set this to what you want the aspect ratio of a button to be */
aspect-ratio: var(--ar);
position: relative;
}
.container>*::after {
content: '';
width: var(--gap);
--arrow: 20px;
/* set this to the height of the arrowhead */
--ha: calc(var(--arrow) / 2);
/* half the height of an arrowhead */
--line: 2px;
/* set this to the width (height) of the lines */
--hl: calc(var(--line) / 2);
/* half the height of a line */
height: var(--arrow);
background-color: black;
clip-path: polygon(0 calc(var(--ha) - var(--hl)), calc(100% - var(--ha)) calc(var(--ha) - var(--hl)), calc(100% - var(--ha)) 0, 100% 50%, calc(100% - var(--ha)) 100%, calc(100% - var(--ha)) calc(var(--ha) + var(--hl)), 0 calc(var(--ha) + var(--hl)));
position: absolute;
top: 50%;
transform: translateY(-50%);
right: calc(-1 * var(--gap));
}
.container>*:nth-child(3n)::after {
width: calc(200% + (2 * var(--gap)) + var(--line));
height: var(--gap);
transform: translateY(0);
top: 100%;
right: calc(50% - (var(--hl)));
clip-path: polygon( 100% 0, 100% calc(50% + var(--hl)), calc(var(--ha) + var(--hl)) calc(50% + var(--hl)), calc(var(--ha) + var(--hl)) calc(100% - var(--ha)), var(--arrow) calc(100% - var(--ha)), var(--ha) 100%, 0 calc(100% - var(--ha)), calc(var(--ha) - var(--hl)) calc(100% - var(--ha)), calc(var(--ha) - var(--hl)) calc(50% - var(--hl)), calc(100% - var(--line)) calc(50% - var(--hl)), calc(100% - var(--line)) 0);
}
.container>*:last-child::after {
display: none;
}
<div class="container">
<button></button><button></button><button></button><button></button><button></button><button></button><button></button><button></button><button></button>
</div>