网格中的按钮,间隙中有箭头

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>