Gradient borders with curves and 3d movement in CSS (Nextjs ticket clone)
Tags: javascript, css, html
Published: 2020-10-28 10:05:47

---
title: Gradient borders with curves and 3d movement in CSS (Nextjs ticket clone)
published: true
description: Building a clone of Nextjs conf ticket with gradient borders and animated 3D on hover
tags: js, css,html
cover_image: https://imgur.com/BinAZIf.png
---

> This post has been originally published on my [blog](https://medhatdawoud.net/blog/gradient-borders-with-curves-and-3d-animation-in-css)

> UPDATE: here is a [Japanese version](https://coliss.com/articles/build-websites/operation/css/gradient-borders-with-curves-and-3d-movement-in-css.html) of this article

On 27th of October 2020 was the first global user [conference](https://nextjs.org/conf) of Next.js, I'm so excited about it as a React developer, That's why I've registered immediately after I knew about it, but what happened after I registered was super interesting, I've got a confirmation message from the conf committee with this URL [https://nextjs.org/conf/tickets/medhatdawoud](https://nextjs.org/conf/tickets/medhatdawoud) which is an interactive ticket, well designed and well animated I'd like to thank the team for designing and developing that, and today we are going to make a clone of it (for learning purposes).

![nextjs conf ticket](https://imgur.com/d3WlrFe.png)

## Challenges

We have quite some challenges here to resolve:

1. building the ticket itself (✅ will just start with a pre-created one)
2. implement the gradient borders.
3. implement the half-circles right and left.
4. implement the animation according to the cursor move.

> Here are 5 other [ways](https://dev.to/medhatdawoud/5-different-ways-to-create-a-ticket-layout-3024) to implement that, you can check them after finishing this article

## Implementation

Let's start with the implementation step by step, hence the final code could be found in this [github repo](https://github.com/semicolon-academy/ui-challenges/tree/master/005-nextjs-ticket) alongside other challenges as well.

### 1. building the ticket itself

As we agreed earlier, that would be ready one, you can find the whole code in the repo, but this is the HTML:

```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>
```

Note: `event-logos.png` is the lower part of the ticket that I took it as a screenshot because that is out of our focus today.

And the CSS is as follow:

```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;
}
```

Now it looks as follow:

![raw challenge](https://imgur.com/yuRs9zY.png)

### 2. implementing the gradient borders

The first goto CSS property for making a gradient or even an image as a border is the `border-image` property, which has great support on all browsers including ie11 as per [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/border-image).

The only problem with using it is that it doesn't support `border-radius` so we cannot use it, unfortunately, and will make some work-around to implement that.

The idea is mainly to use a `div` inside another `div`, let's call them parent div and child div, you can easily add an image or gradient color in our case as background to the parent div, then give a solid color to the child div for example solid black in our case, then give the parent div `padding` of the width of the border you want, in our case `5px`, and technically what the `padding` is doing is to put a space between the border and the content inside the `element`, so it will press the child div with `5px` from all directions, and that will enable 5px to show from the parent div as if they are a border to the child div.

well, let's implement that, we have a parent child which is `.ticket-visual_visual` then we can give it a background with the desired gradient border colors, after getting the 4 colors from the main conf website and create them as custom properties as follow:

```css
: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)
  );
}
```

Notice using `linear-gradient` the first param is `to right` as we needed to have that gradient from left to right.

Now we need to make the child div with solid background as we agreed, the child div here is `.ticket-visual-wrapper`, so let's give it a background:

```css
.ticket-visual-wrapper {
  background: var(--background); // --background is #000
}
```

Now we have made it with that work-around for a gradient border, let's now try to give them border radius:

```css
.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;
}
```

and the current result should be:

![border gradient with curves](https://imgur.com/4jzwqIx.png)

Well, we reached a good stage, for now, we have made a curved border with gradient color.

### 3. implementing the half-circles right and left

> Note that there are several different ways to reach the same result that might be better as well than this solution feel free to write you suggestion in a comment :) 

With the same idea, we used before we need to use `pseudo-elements` of the parent div as parent elements and for the child div as child elements.

so basically will use `:before` and `:after` pseudo-elements as follow:

```css
.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;
}
```

As you can notice, we treat them as divs and positioned them in the middle left and right of the card, also give both of them the extremes of the gradient colors, the left one takes the first color `--color1` as background and the right one takes `--color4` as background, so the result now should be as follow:

![](https://imgur.com/8kJp3EL.png)

Then we need to add a child circle for each of them with a solid color (black), let's add `pseudo-elements` for the `.ticket-visual-wrapper` as well, but first let's add `position: relative` to it:

```css
.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;
}
```

As you see, we made 2 smaller circles `50px X 50px` then the parent ones `60px X 60px` and the background here for both are the color of the background `--background` which is black, the last notice here is that I give them `z-index: 3` to make them get elevated on top of the parent `pseudo-elements`.

The current result:

![](https://imgur.com/vsjA1nq.png)

The only remaining thing is to hide the outer halves of the circles, TBW I found that having something like a cover for them could be a good solution, so I decided to add 2 divs that could be used as covers inside `.ticket-visual_visual` as follow:

```html
<div class="left"></div>
<div class="right"></div>
```

and in CSS as they are inside a `position: relative` div, by giving them `position: absolute` they will be positioned well:

```css
.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;
}
```

giving them background black, and `z-index: 4` to be on to and cover the halves of the circles, the final result is:

![](https://imgur.com/BinAZIf.png)

Now the design is complete like the one implemented in the conf website.

### 4. implementing the animation according to the cursor move

Now is the time for a bit of JavaScript, we simply need to calculate a variable with is the position of the cursor (mouse) every time we move so we can add a listener to the `mousemove` event.

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

I decided to add that in an inline script tag in the same HTML file because it doesn't require a separate file.

before the listening we need to select the ticker element and get its bounding rect as well, to calculate the center point of the ticket element as follow:

```js
const ticketElm = document.getElementById("ticket")
const { x, y, width, height } = ticketElm.getBoundingClientRect()
const centerPoint = { x: x + width / 2, y: y + height / 2 }
```

then inside the `mousemove` event lister we need to add some code to transform that ticket, simply we can add some calculations for the degree that we will use for rotation as follow:

```js
const degreeX = (e.clientY - centerPoint.y) * 0.008
const degreeY = (e.clientX - centerPoint.x) * -0.008
```

Note that this calculation means: we get the difference between the current mouse position and the center point we calculated earlier, then multiply them by a very small number `0.008`, I got it by trying and error until I feel that fit best.

Then we can use these calculated degrees to make the transformation:

```js
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)`
})
```

at line `5` you can find that we simply set the `perspective` of the element to `1000px` which is a big number to make it move very smooth without rotation, also we used the rotation of `x` and `y` based on the calculated degrees.

Then the final result will be:

![3d animate on hover](https://imgur.com/Ckq2BV8.gif)

And, we're done here, you might notice some shiny gradient on moving the mouse, but that's for you for homework to make the ticket look glossy, please let me know if you did.

## Conclusion

I've enjoyed writing this article, and I hope you enjoyed reading it as well: we've learned from it multiple things or at least I hope so:

- How to work-around and make a gradient border with border-radius in place
- How to implement a half-circle with a gradient border
- How to use `perspective` in implementing a 3D animation
- How to think about the calculation of the variable
- All code is on [Github](https://github.com/semicolon-academy/ui-challenges) go check it out, fork, clone, and do your homework 😉.

Finally, feel free to share it or discuss it with me on [Twitter](https://twitter.com/Med7atDawoud) if you want any help, or follow and let's be friends.

If you understand Arabic, here is an explanation step by step in an Arabic tutorial:
https://youtu.be/BfAydRvM-vk

Tot ziens 👋