这种css复杂(且动态)的UI的效果该如何实现?

图一:
1、动态时间轴的长短根据起止时间来定的,就是说如果时间更长,结束点有可能继续往下(拐弯)走;
2、灰色表示整个轴的长度,渐变(彩)色表示实际进度;
3、时间轴上的小圆点也是动态的,表示某一天对应的位置;
4、点击时间轴上的白色小圆点,会弹出对应的下拉卡片,卡片有堆叠效果,表示可以继续展开,点击卡片上的蓝色小圆点,则继续展开内容;

图二:
1、点击卡片上蓝色小圆点,展示详细内容,时间轴及其他卡片处于高斯模糊状态,以突出当前选中的卡片及其详细内容。

实现要求:
图一、图二里面的说明就是UI需要实现的效果,另外就是整个时间轴内容需要可以方法、缩小、拖拽;

思路及想法:
昨天研究了一天,发现用常规的 css 技术连 “图一” 的时间轴都无法做到,所以来请教下大家有什么要建议,我的想法有两种:
1、通过canvas技术来实现时间轴(灰色轨迹和渐变色的实际进度)图,把canvas图当作背景图片,然后通过绝对定位,把开始结束、每天一天对应的点都打上去,然后放到缩小、拖拽等所有其他逻辑通过cssjs技术实现;
2、整个效果交互都通过canvas来实现,但是对于canvas美什么经验,不敢轻易使用;

请教下大家有什么好的意见或建议,或者好的canvas操作库等,谢谢!

阅读 4.2k
4 个回答

https://jsrun.net/3YJKp/edit
SVG画起来感觉还好,难到不难,就是比较繁琐。。。 最好找下看有什么第三方库,简化操作

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.47/vue.global.js"></script>
</head>

<body>
  <div id="app">
    <svg class="test" width="500" height="300" style="background-color: rgb(232, 234, 238);">
      <!-- 渐变色 -->
      <defs>
        <linearGradient id="gradient" x1="0%" y1="0%" x2="0%" y2="100%">
          <stop offset="0%" style="stop-color:#5897ee;"></stop>
          <stop offset="100%" style="stop-color:#eda378;"></stop>
        </linearGradient>
      </defs>

      <!-- 轨道 -->
      <path v-for="p in paths" :key="p.index" id="path" d=" M 20 20 L 430 20 C 480 20 480 120 430 120 L 430 120 L 20 120" :stroke="p.hex" :stroke-width="p.index" stroke-linecap="round" stroke-linejoin="round" fill="transparent"></path>

      <!-- 进度 -->
      <path d=" M 20 20 L 430 20 C 480 20 480 120 430 120 L 430 120 L 200 120" stroke="url(#gradient)" stroke-width="21" stroke-linecap="round" stroke-linejoin="round" fill="transparent"></path>

      <!-- 圈 -->
      <circle cx="20" cy="170" r="10" fill="#2274fa" />
      <circle cx="50" cy="170" r="10" fill="#e78d48" />

      <circle cx="80" cy="170" r="6" fill="#fff" />
      <path d="M 76 170 H 84" stroke="#2274fa" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" fill="transparent"></path>

      <circle cx="100" cy="170" r="6" fill="#fff" />
      <path d="M 96 170 H 104" stroke="#2274fa" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" fill="transparent"></path>
      <path d="M 100 166 V 174" stroke="#2274fa" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" fill="transparent"></path>

      <!-- 文本 -->
      <path d="M 120 140 H 250 V 200 H 120 Z" stroke="transparent" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" fill="#fff"></path>
      <text x="140" y="160" font-family="Verdana" font-size="12">
        <tspan x="140" y="160" fill="red">不支持自动换行</tspan>
        <tspan x="140" y="180">这是第二行</tspan>
      </text>
    </svg>
  </div>

  <script>
    const { createApp } = Vue

    var paths = getPaths('#eeeeee', '#aaaaaa', 23)

    createApp({
      data() {
        return {
          paths: paths,
          message: 'Hello Vue!'
        }
      }
    }).mount('#app')

    function getPaths(color1, color2, height) {
      var paths = []

      var color = {
        start: hexToRGB(color1),
        end: hexToRGB(color2),
        height: height
      }

      var h = color.height - 1
      for (let i = h; i >= 0; i--) {
        var c = {
          r: color.start.r + (color.end.r - color.start.r) / h * i,
          g: color.start.g + (color.end.g - color.start.g) / h * i,
          b: color.start.b + (color.end.b - color.start.b) / h * i,
        }

        paths.push({
          index: i,
          rgb: `rgb(${c.r}, ${c.g}, ${c.b})`,
          hex: rgbToHex(c.r, c.g, c.b)
        })

      }
      console.log(JSON.stringify(paths, null, 2))
      return paths
    }

    function hexToRGB(hex) {
      var r = parseInt(hex.slice(1, 3), 16);
      var g = parseInt(hex.slice(3, 5), 16);
      var b = parseInt(hex.slice(5, 7), 16);

      return { r: r, g: g, b: b };
    }

    function rgbToHex(_r, _g, _b) {
      var [r, g, b] = [Math.round(_r), Math.round(_g), Math.round(_b)]
      var rHex = r.toString(16);
      rHex = rHex.length == 1 ? "0" + rHex : rHex;

      var gHex = g.toString(16);
      gHex = gHex.length == 1 ? "0" + gHex : gHex;

      var bHex = b.toString(16);
      bHex = bHex.length == 1 ? "0" + bHex : bHex;

      return "#" + rHex + gHex + bHex;
    }
  </script>
</body>

</html>

CSS实现的思路是有的,多个弯可以用 gird 布局来处理 border 的弯折,但是会复杂。
Canvas来实现确实会好很多,但是相对来说如果没有canvas经验的话,付出的时间成本会更大。

至于毛玻璃效果可以直接用 filter: blur() 来实现,这个反而是实现起来最简单的。

canvas不熟的话可以考虑用 svg 来画,配合css做动画是可以实现的

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏