其原理,还是利用border属性来实现边线两端的斜边。两个边交叉的地址即为斜边。例如,以下是一个宽100px,边40px的正方形。观察两条相邻边相交的地方,是一条斜边。

<div class="box"></div> 
.box{
  margin:20px;
  width: 100px;
  height: 100px;
  border-left: 40px solid red;
  border-right: 40px solid red;
  border-top: 40px solid black;
  border-bottom: 40px solid black;
}

但是,这样有个问题是两个边之间没有空隙,无法达到模拟效果,所以七条边需要各自模拟。

首先,假设每个“管”有个一个编号

我们用”.d1″, “.d2″……来命名各个边的样式。我们分别给各个别设定宽、高、位移和定位。

例如1号边:

.d1 {
    height: 0;
    width: calc($boxW - $space * 2); // 线宽度, $space是两个管之间相邻的缝隙,减掉$space*2 正是为了减掉缝隙宽度。
    position: absolute;              // 以下四行是定位,固定在顶部
    top: 0;
    left: math.div($space, 1);
    right: 0;
    border: $with solid $color;      // 这四行是画边线。
    border-left-color: transparent;
    border-right-color: transparent;
    border-bottom: 0;
  }

完整代码如下。

样式代码(SASS语法)

@use "sass:math";

.nixie-tube {
  $size: 1em;           // 字符宽度
  $with: 0.2em;         // 线宽
  $color: #000;       // 线颜色
  $space: 0.15em;       // 线之间的间距

  $boxW: $size * 0.8;   
  $boxH: $size * 1.2;
  margin-right: 2px;

  height: $boxH;
  width: $boxW;
  position: relative;
  display: inline-block;
  line-height: 1;
  &.nixie-text-wrapper {
    width: auto;
  }
  /* 数值 1 靠左偏移 3/8, 以便看起来不要跟右侧的数字太近 */
  &.n-1 {
    transform: translateX(calc($boxW * -0.375));
  }
  /* 非数字类型,增加视觉误差 */
  .nixie-text {
    font-size: calc($boxH + 0.1em);
  }
  /* 每一个管的位置、宽度 */
  .d1 {
    height: 0;
    width: calc($boxW - $space * 2);
    position: absolute;
    top: 0;
    left: math.div($space, 1);

    right: 0;
    border: $with solid $color;
    border-left-color: transparent;
    border-right-color: transparent;
    border-bottom: 0;
  }
  .d2 {
    height: $with;
    width: calc($boxW - $space * 2);

    border: none;
    position: absolute;
    top: calc(($boxH - $with) / 2);
    left: math.div($space, 1);
    /* 由于线两端不是斜边,所以不能用常规方法。改用before和after两个伪元素来模拟三角形,固定在线的两端。 */
    &:before {
      content: " ";
      height: 0;
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      border: calc(math.div($with, 2) + 0.6px) solid $color;
      border-left-color: transparent;
      border-right-color: transparent;
      border-top: 0;
    }
    &:after {
      content: " ";
      height: 0;
      position: absolute;

      left: 0;
      right: 0;
      bottom: 0;
      // top: calc(math.div($with, 2) - 0.5px);
      border: calc(math.div($with, 2) + 0.6px) solid $color;
      border-left-color: transparent;
      border-right-color: transparent;
      border-bottom: 0;
    }
  }
  .d3 {
    height: 0;
    width: calc($boxW - $space * 2);
    position: absolute;
    bottom: 0;
    left: math.div($space, 1);

    right: 0;
    border: $with solid $color;
    border-left-color: transparent;
    border-right-color: transparent;
    border-top: 0;
  }
  .d4,
  .d6 {
    width: 0;
    position: absolute;
    top: 0;
    height: math.div($boxH, 2);
    left: 0;

    border: $with solid $color;
    border-top-color: transparent;
    border-bottom-color: transparent;
    border-right: 0;
  }
  .d5,
  .d7 {
    width: 0;
    position: absolute;
    top: 0;
    height: math.div($boxH, 2);
    right: 0;

    border: $with solid $color;
    border-top-color: transparent;
    border-bottom-color: transparent;
    border-left: 0;
  }
  .d6,
  .d7 {
    top: calc(($boxH) / 2);
  }

}

react部分相对简单

import './index.scss'


export default function NixieTube (props) {

    let number = props.number;

    // 如果不是数字,则直接显示文字。
    if (isNaN(number)) {
        return (<div className='nixie-tube nixie-text-wrapper'>
            <div className='nixie-text'>{number}</div>

        </div>)
    }
    number = parseInt(number);
    return (
        <div className={'nixie-tube n-' + number}>
            {[2, 3, 5, 6, 7, 8, 9, 0].includes(number) && <div className='d1'></div>}
            {[2, 3, 4, 5, 6, 8, 9].includes(number) && <div className='d2'></div>}
            {[2, 3, 5, 6, 8, 9, 0].includes(number) && <div className='d3'></div>}
            {[4, 5, 6, 8, 9, 0].includes(number) && <div className='d4'></div>}
            {[1, 2, 3, 4, 7, 8, 9, 0].includes(number) && <div className='d5'></div>}
            {[2, 6, 8, 0].includes(number) && <div className='d6'></div>}
            {[1, 3, 4, 5, 6, 7, 8, 9, 0].includes(number) && <div className='d7'></div>}

        </div>

    )

}

使用方法:

由于以上实现仅实现了单个字符的显示,建议二次封装,可以直接显示一段文字

import Nixietube from "@/components/nixietube/index";

export default function (props) {

    const renderNixietube = (text) => {

        if (!text) {
            return null;
        }
        let chars = text.split('').map((c, i) => {
            return <Nixietube number={c} key={i}></Nixietube>
        });

        return <div className="nixietube-container">{chars}</div>

    }

    return renderNixietube("这是数码管:123456789" );
}

实际显示效果

其他要注意的地方。字符“1”比较狭窄,看起来会贴近右侧数字,同时跟左侧数字距离较远,看起来会很奇怪。所以写样式时,适当将字符“1”向左偏移30%左右,这样看起来会协调很多。