C

CSS 中带有曲线和 3D 运动的渐变边框(Nextjs 票证克隆)

2025-05-25

CSS 中带有曲线和 3D 运动的渐变边框(Nextjs 票证克隆)

这篇文章最初发表在我的博客上

更新:这是本文的日文版本

2020 年 10 月 27 日是 Next.js 的第一次全球用户大会,作为一名 React 开发人员,我对此感到非常兴奋,这就是为什么我在得知消息后立即注册的原因,但注册后发生的事情非常有趣,我收到了会议委员会的确认消息,网址为https://nextjs.org/conf/tickets/medhatdawoud,这是一张互动票,设计精良,动画效果很好,我要感谢团队设计和开发它,今天我们将对它进行克隆(用于学习目的)。

nextjs 会议票

挑战

我们在这里面临不少挑战需要解决:

  1. 自行构建票据(✅ 将从预先创建的票据开始)
  2. 实现渐变边框。
  3. 左右画半圆。
  4. 根据光标移动实现动画。

这里还有其他 5 种方法可以实现这一点,你可以在读完本文后查看它们

执行

让我们一步一步地开始实现,因此最终的代码可以在这个github repo中找到,同时也可以找到其他挑战。

1. 创建票证本身

正如我们之前同意的那样,这将是准备好的,您可以在 repo 中找到整个代码,但这是 HTML:

<div class="ticket-visual_visual" id="ticket">
  <div class="left"></div>
  <div class="right"></div>
  <div class="ticket-visual-wrapper">
    <div class="ticket-visual_profile">
      <div class="ticket-profile_profile">
        <img
          src="https://github.com/medhatdawoud.png"
          alt="medhatdawoud"
          class="ticket-profile_image"
        />
        <div class="ticket-profile_text">
          <p class="ticket-profile_name">Medhat Dawoud</p>
          <p class="ticket-profile_username">
            <span class="ticket-profile_githubIcon">
              <img src="./github.svg" alt="" />
            </span>
            medhatdawoud
          </p>
        </div>
      </div>
      <div class="ticket-event">
        <img src="./event-logos.png" />
      </div>
    </div>
    <div class="ticket-visual_ticket-number-wrapper">
      <div class="ticket-visual_ticket-number">№ 014747</div>
    </div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

注意:event-logos.png票的下半部分是我截取的屏幕截图,因为这不是我们今天的重点。

CSS 如下:

:root {
  --size: 1;
  --background: #000;
}

body {
  background: var(--background);
  color: white;
  font-family: Arial, Helvetica, sans-serif;
}

* {
  box-sizing: border-box;
}

.ticket-visual_visual {
  width: 650px;
  height: 320px;
  margin: 100px auto;
  position: relative;
  transition: all 300ms cubic-bezier(0.03, 0.98, 0.53, 0.99) 0s;
  border: 5px solid #fff;
}

.ticket-visual-wrapper {
  width: 100%;
  height: 100%;
}

.ticket-visual_profile {
  padding: calc(39px * var(--size)) calc(155px * var(--size)) calc(
      39px * var(--size)
    ) calc(58px * var(--size));
}

.ticket-profile_text {
  margin: 0;
}

.ticket-profile_profile {
  display: flex;
  flex-direction: row;
}

.ticket-event {
  margin-top: 25px;
  margin-left: -10px;
}

.ticket-profile_image {
  width: calc(82px * var(--size));
  height: calc(82px * var(--size));
  border-radius: 50%;
}

.ticket-profile_name {
  font-size: calc(32px * var(--size));
  margin: 10px 0 5px 20px;
  font-weight: 700;
}

.ticket-profile_username {
  margin: 0 0 5px 20px;
  color: #8a8f98;
  display: flex;
}

.ticket-profile_githubIcon img {
  width: 18px;
  height: 18px;
  margin-right: 5px;
}

.ticket-visual_ticket-number-wrapper {
  position: absolute;
  right: 35px;
  bottom: 0;
}

.ticket-visual_ticket-number {
  transform: rotate(90deg) translateY(calc(100px * var(--size)));
  transform-origin: bottom right;
  font-size: calc(40px * var(--size));
  font-weight: 700;
  text-align: center;
  padding-bottom: 35px;
  width: calc(320px - 10px);
  border-bottom: 2px dashed #333;
}
Enter fullscreen mode Exit fullscreen mode

现在看起来如下:

原始挑战

2. 实现渐变边框

用于制作渐变或甚至将图像作为边框的第一个 CSS 属性是该border-image属性,根据MDN ,该属性在包括 ie11 在内的所有浏览器上都得到了很好的支持。

使用它的唯一问题是它不支持,border-radius所以很遗憾我们无法使用它,并且会采取一些变通措施来实现这一点。

这个想法主要是div在另一个里面使用一个div,我们把它们称为父 div 和子 div,您可以轻松地在我们的例子中添加图像或渐变色作为父 div 的背景,然后为子 div 提供纯色,例如在我们的例子中为纯黑色,然后为父 div 提供padding您想要的边框宽度,在我们的例子中5px,从技术上讲,所做padding的是在边框和内部内容之间放置一个空间element,因此它将从各个方向压制子 div 5px,并且将使 5px 从父 div 显示,就好像它们是子 div 的边框一样。

好吧,让我们实现它,我们有一个父子,然后.ticket-visual_visual我们可以给它一个具有所需渐变边框颜色的背景,从主会议网站获取 4 种颜色并将它们创建为自定义属性,如下所示:

:root {
  // rest of variable
  --color1: #d25778;
  --color2: #ec585c;
  --color3: #e7d155;
  --color4: #56a8c6;
}

.ticket-visual_visual {
  // other code here
  background: linear-gradient(
    to right,
    var(--color1),
    var(--color2),
    var(--color3),
    var(--color4)
  );
}
Enter fullscreen mode Exit fullscreen mode

请注意,使用linear-gradient第一个参数是to right因为我们需要从左到右进行渐变。

现在我们需要按照我们约定的方式为子 div 制作一个实体背景,这里的子 div 是.ticket-visual-wrapper,所以让我们给它一个背景:

.ticket-visual-wrapper {
  background: var(--background); // --background is #000
}
Enter fullscreen mode Exit fullscreen mode

现在我们已经用这个变通方法制作了渐变边框,现在让我们尝试给它们设置边框半径:

.ticket-visual_visual {
  // other styles
  background: linear-gradient(
    to right,
    var(--color1),
    var(--color2),
    var(--color3),
    var(--color4)
  );
  border-radius: 20px;
}

.ticket-visual-wrapper {
  // other styles
  background: var(--background);
  border-radius: 15px;
}
Enter fullscreen mode Exit fullscreen mode

当前结果应该是:

带曲线的边框渐变

好了,我们到达了一个很好的阶段,现在,我们已经制作了一个具有渐变颜色的弯曲边框。

3. 左右半圆的实现

请注意,有几种不同的方法可以达到相同的结果,这些结果可能比此解决方案更好,请随意在评论中写下您的建议:)

和我们之前使用的想法一样,我们需要使用pseudo-elements父div作为父元素,使用子div作为子元素。

因此基本上将使用:before如下:after伪元素:

.ticket-visual_visual:before {
  content: "";
  display: block;
  position: absolute;
  top: 130px;
  left: -30px;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: var(--color1);
  z-index: 2;
}

.ticket-visual_visual:after {
  content: "";
  display: block;
  position: absolute;
  top: 130px;
  right: -30px;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: var(--color4);
  z-index: 2;
}
Enter fullscreen mode Exit fullscreen mode

正如你所注意到的,我们将它们视为 div,并将它们放置在卡片的中间左侧和右侧,同时赋予它们两个渐变颜色的极端值,左侧以第一种颜色--color1作为背景,右侧以第一种颜色--color4作为背景,因此现在的结果应该如下所示:

然后我们需要为每个圆圈添加一个纯色(黑色)的子圆圈,让我们也pseudo-elements为它添加.ticket-visual-wrapper一个,但首先让我们position: relative为它添加:

.ticket-visual-wrapper {
  width: 100%;
  height: 100%;
  background: var(--background);
  border-radius: 15px;
  position: relative;
}

.ticket-visual-wrapper:before {
  content: "";
  display: block;
  position: absolute;
  top: 130px;
  left: -30px;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: var(--background);
  z-index: 3;
}

.ticket-visual-wrapper:after {
  content: "";
  display: block;
  position: absolute;
  top: 130px;
  right: -30px;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: var(--background);
  z-index: 3;
}
Enter fullscreen mode Exit fullscreen mode

如您所见,我们制作了两个50px X 50px比父级圆圈小的圆圈60px X 60px,并且这两个圆圈的背景都是--background黑色,最后的通知是,我给它们z-index: 3使它们高于父级圆圈pseudo-elements

目前结果:

剩下的唯一事情就是隐藏圆圈的外半部分,TBW 我发现为它们添加类似封面的东西可能是一个很好的解决方案,所以我决定添加 2 个可以用作封面的 div,.ticket-visual_visual如下所示:

<div class="left"></div>
<div class="right"></div>
Enter fullscreen mode Exit fullscreen mode

并且在 CSS 中,由于它们位于position: relativediv 内,因此通过赋予它们,position: absolute它们将被定位在良好的位置:

.left {
  position: absolute;
  top: 110px;
  left: -50px;
  width: 50px;
  height: 100px;
  background: var(--background);
  z-index: 4;
}

.right {
  position: absolute;
  top: 110px;
  right: -50px;
  width: 50px;
  height: 100px;
  background: var(--background);
  z-index: 4;
}
Enter fullscreen mode Exit fullscreen mode

给它们背景黑色,并z-index: 4覆盖圆圈的两半,最终结果是:

现在设计已经完成,就像在 conf 网站中实现的那样。

4. 根据光标移动实现动画

现在是时候使用一些 JavaScript 了,我们只需要计算一个变量,该变量是每次移动时光标(鼠标)的位置,这样我们就可以为事件添加一个监听器mousemove

window.addEventListener("mousemove", e => {
  // some code to run every time a user moves the mouse cursor
})
Enter fullscreen mode Exit fullscreen mode

我决定在同一个 HTML 文件中的内联脚本标签中添加它,因为它不需要单独的文件。

在监听之前,我们需要选择股票行情元素并获取其边界矩形,以计算股票行情元素的中心点,如下所示:

const ticketElm = document.getElementById("ticket")
const { x, y, width, height } = ticketElm.getBoundingClientRect()
const centerPoint = { x: x + width / 2, y: y + height / 2 }
Enter fullscreen mode Exit fullscreen mode

然后在mousemove事件列表器中,我们需要添加一些代码来转换该票证,只需添加一些用于旋转的度数计算即可,如下所示:

const degreeX = (e.clientY - centerPoint.y) * 0.008
const degreeY = (e.clientX - centerPoint.x) * -0.008
Enter fullscreen mode Exit fullscreen mode

请注意,这个计算的意思是:我们得到当前鼠标位置和我们之前计算的中心点之间的差值,然后将它们乘以一个非常小的数字0.008,我通过不断尝试和错误来得到它,直到我觉得最合适。

然后我们可以使用这些计算出的度数来进行变换:

window.addEventListener("mousemove", e => {
  const degreeX = (e.clientY - centerPoint.y) * 0.008
  const degreeY = (e.clientX - centerPoint.x) * -0.008

  ticketElm.style.transform = `perspective(1000px) rotateX(${degreeX}deg) rotateY(${degreeY}deg)`
})
Enter fullscreen mode Exit fullscreen mode

在行处5您可以发现我们只是将perspective元素的设置为1000px一个大数字以使其移动非常平滑而无需旋转,并且我们还使用了基于计算度数的x和的旋转。y

那么最终结果将是:

悬停时呈现 3D 动画

现在,我们已经完成了,您可能会注意到移动鼠标时会出现一些闪亮的渐变,但这是您的家庭作业,目的是让票看起来有光泽,如果您这样做了,请告诉我。

结论

我很高兴写这篇文章,我希望你也喜欢阅读它:我们从中学到了很多东西,或者至少我希望如此:

  • 如何解决并制作具有 border-radius 的渐变边框
  • 如何实现带有渐变边框的半圆
  • 如何使用perspective实现 3D 动画
  • 如何思考变量的计算
  • 所有代码都在Github上,去查看、分叉、克隆并做好你的功课 😉。

最后,如果您需要任何帮助,请随时在Twitter上与我分享或讨论,或者关注我并让我们成为朋友。

如果您懂阿拉伯语,这里有阿拉伯语教程中的逐步解释:
https://youtu.be/BfAydRvM-vk

孩子们👋

文章来源:https://dev.to/medhatdawoud/gradient-borders-with-curves-and-3d-movement-in-css-nextjs-ticket-clone-3cho
PREV
开发人员一定会喜欢的 6 个网站
NEXT
在网页上轻松使用暗黑模式