Combining bezier curves and jQuery coordinates.

Written by ethan.jarrell | Published 2018/01/06
Tech Story Tags: web-development | javascript | jquery | programming | html

TLDRvia the TL;DR App

I’ve been working on a new portfolio site recently. I had this idea in my head of what I wanted it to look like, and I had to try a few things I wasn’t familiar with in order to get it to work. Here’s the site, and if you want to try something similar, I’ll explain my thought process on how I got it working: https://ethansportfolio.herokuapp.com/

As you can see, button links to each of my sites float around the screen. As the float, they create a geometric shape in the background, which moves and adjusts with the movement of the floating buttons. That’s more or less what I had in mind when I started.

My thought process was this:

  1. I needed to create the HTML elements.
  2. I need to animate the HTML elements.
  3. I need to create an svg polygon element, whose points are based on the animated elements.

The HTML

<div class="linkContainer"><a href="/portfolioB"><div id="site1" value="Donald Trump Site" class="site1 active"></div></a><a href="/portfolioF"><div id="site2" value="Simple Piano" class="site2"></div></a><a href="/portfolioE"><div id="site3" value="Weather App" class="site3"></div></a><a href="/portfolioA"><div id="site4" value="Search & Play Music" class="site4"></div></a><a href="/portfolioD"><div id="site5" value="Presentation Maker" class="site5"></div></a><a href="/portfolioG"><div id="site6" value="DrayNori" class="site6"></div></a><a href="/portfolioC"><div id="site7" value="Form Tests" class="site7"></div></a></div>

Pretty Basic. Now, I’m going to create an empty Div that will hold my polygon in the background.

<div class="polygonContainer"><div id="polygon"></div></div>

Sweet. Looking good so far. Next, I need to animate each link. I went through several possibilities to make this happen. Initially I went about calculating the screen/window size, and then moving the elements around in JavaScript based on the current window dimensions. That avenue didn’t go so well. Finally I came across an article about using the bezier curve. I had used a bezier curve before when doing transitions, but never with the keyFrame animations in CSS, but it looked promising. It’s easy to see some animations quickly, after popping the code in, but for me, since this was my first time, it took quite a bit of finesse to get the animations to look sort of how I had envisioned them. Because I wanted each element to float differently than the rest, I created a different keyFrame animation for each element, and adjusted each one separately. Here’s the boilerPlate code I used for the keyframe animations in CSS:

div#site1.site1 {z-index: 2;position: static;transition-timing-function: cubic-bezier(0.64, 0.57, 0.67, 1.53);transition-duration: 1s;height: 10%;display: flex;align-items: center;-webkit-animation: xAxis1 80.5s infinite cubic-bezier(40.9, 0.9, 20.1, 50.0);animation: yAxis1 70.5s infinite cubic-bezier(0.7, 0.27, 0.57, 0.1);-webkit-animation: yAxis1 50.5s infinite cubic-bezier(40.7, 0.27, 20.57, 50.64);animation: xAxis1 80.5s infinite cubic-bezier(.002, 0.004, 0.1, 0.1);}

div#site1.site1::after {transition-timing-function: cubic-bezier(0.64, 0.57, 0.67, 1.53);transition-duration: 1s;font-family: 'Sedgwick Ave Display', cursive;background-size: cover;object-fit: cover;object-position: top;cursor: pointer;border: solid black;border-left-width: 2.2px;border-top-width: 1.8px;border-right-width: 1.5px;border-bottom-width: 1.2px;z-index: 2;font-size: 20px;font-weight: bold;padding: 10px;width: 250px;color: white;text-shadow: rgb(11, 16, 18) 2px 0px 0px, rgb(11, 16, 18) 1.75517px 0.958851px 0px, rgb(11, 16, 18) 1.0806px 1.68294px 0px, rgb(11, 16, 18) 0.141474px 1.99499px 0px, rgb(11, 16, 18) -0.832294px 1.81859px 0px, rgb(11, 16, 18) -1.60229px 1.19694px 0px, rgb(11, 16, 18) -1.97998px 0.28224px 0px, rgb(11, 16, 18) -1.87291px -0.701566px 0px, rgb(11, 16, 18) -1.30729px -1.5136px 0px, rgb(11, 16, 18) -0.421592px -1.95506px 0px, rgb(11, 16, 18) 0.567324px -1.91785px 0px, rgb(11, 16, 18) 1.41734px -1.41108px 0px, rgb(11, 16, 18) 1.92034px -0.558831px 0px;background-color: white;display: flex;justify-content: center;align-items: center;content: attr(value);display: block;-webkit-animation: xAxis1 80.5s infinite cubic-bezier(40.9, 0.9, 20.1, 50.0);animation: xAxis1 80.5s infinite cubic-bezier(.002, 0.004, 0.1, 0.1);animation: yAxis1 70.5s infinite cubic-bezier(0.7, 0.27, 0.57, 0.1);-webkit-animation: yAxis1 50.5s infinite cubic-bezier(40.7, 0.27, 20.57, 50.64);}

div#site1.site1:hover::after {background-image:url('trump.png');z-index: 2;transition-timing-function: cubic-bezier(0.64, 0.57, 0.67, 1.53);transition-duration: 1s;transform: scale(1.2);filter: contrast(150%) brightness(140%);}

div#site1.site1:hover{flex-basis:0;flex-grow: 2;}

@-webkit-keyframes yAxis1 {50% {z-index: 2;-webkit-animation-timing-function: cubic-bezier(200.02, 200.01, 200.21, 400);animation-timing-function: cubic-bezier(200.02, 200.01, 200.21, 400);-webkit-transform: translateY(80vh);transform: translateY(80vh);}}@-webkit-keyframes xAxis1 {50% {z-index: 2;-webkit-animation-timing-function: cubic-bezier(200.3, 100.27, 400.07, 0.01);animation-timing-function: cubic-bezier(200.3, 100.27, 400.07, 0.01);-webkit-transform: translateX(80vw);transform: translateX(80vw);}}

Now, if you have two or three elements on screen you want to animate in a somewhat random fashion, similar to what I did, it’s pretty easy to edit this boilerPlate code to do so. The x and y axis both have a transform: translate option. I’ve set each of my elements to a vh or vw setting. This basically means they will animate to that point in the viewport before going back the other direction, sort of like those old school screen savers with balls that would bounce around the screen. Each animation also has a time, in seconds. So since this element is set to about 50s and 80vw, that means that it will animate to roughly 80% of the viewport in 50 seconds. If you create a second element, you could adjust it to 90vw and 20s, and although it’s pretty similar, the animations will look quite different when compared to each other, since they are moving at different speeds, and taking slightly different paths. Keep in mind, this is my first time doing this, so I’m still familiarizing myself with it all. With that starting point, you could add several elements and simply adjust the translate and the animation timing to get several paths for your animations.

Coordinates

Naturally, now that I have my elements animating on the screen, my next goal is to track their coordinates. I want to use these coordinates as the points for an svg polygon element. I found several ways to do this in JavaScript, before stumbling across this absolutely GOLD function in jquery.

getBoundingClientRect();

Using this will get you not only the x and y coordinates of any element on screen, but also the top and left, as well as height and width of any element. It’s super handy! So what I wanted to do was track each of my seven elements. There’s probably a better way to do this, but I figured, since they’re constantly in motion, I want to check the new coordinates, every few milliseconds. I found that if I set a setinterval function at 50 milliseconds, that was a little too choppy…as well as anything higher than 50 milliseconds. And anything less than about 5 got to be way too intense. About 20 milliseconds seemed to be the magic number for me. So I started by creating a setinterval function, and inside it using the getBoundingClientRect(); function for each element, which will give me the coordinates of each element, every 20 milliseconds. Here’s how that looks:

setInterval(function(){

let element1 = site1.getBoundingClientRect();

let element2 = site2.getBoundingClientRect();

let element3 = site3.getBoundingClientRect();

let element4 = site4.getBoundingClientRect();

let element5 = site5.getBoundingClientRect();

let element6 = site6.getBoundingClientRect();

let element7 = site7.getBoundingClientRect();

Now, I can console log each element to make sure I’m getting the coordinates. This next part is going to look a little crazy, but hang with me. So I dynamically create an svg element, with each polygon inside, and I grab the element coordinates using backticks and use them as the points for each polygon. Normally that would look something like this:

points=”${ element1.x,element1.y element2.x,element2.y......

And so forth. But because I wanted a little bit of randomness in the polygon, I switched it up so that the coordinates don’t match exactly for each coordinate pair. I might use element1.x and element7.y as the coordinate pair instead of element1.x and element1.y. Anyway, here’s what I used:

let polygon1 = `<svg class="polygonContainer" ><polygon style="stroke:black;stroke-width:${stroke1}" points="${element1.x},${element7.y} ${element2.x},${element6.y} ${element3.x},${element5.y} ${element1.x},${element4.y} ${element2.x},${element5.y} ${element3.x},${element6.y} ${element4.x},${element7.y}" class="white" id="c"/>

<polygon style="stroke:lightgray;stroke-width:${stroke2}" points="${element1.x+5},${element3.y+15} ${element6.x+5},${element3.y+15} ${element7.x+5},${element3.y+15} ${element1.x+5},${element4.y+15} ${element6.x+5},${element5.y+15} ${element5.x+5},${element2.y+15} ${element1.x+5},${element4.y+15}" class="white4" id="f"/>

<polygon style="stroke:gray;stroke-width:${stroke3}" points="${element7.x},${element7.y+10} ${element1.x},${element1.y+10} ${element4.x},${element4.y+10} ${element5.x},${element5.y+10} ${element6.x},${element6.y+10} ${element3.x},${element3.y+10} ${element1.x},${element1.y+10}" class="white2" id="d"/>

<polygon style="stroke:darkgray;stroke-width:${stroke4}" points="${element3.x},${element1.y+20} ${element3.x},${element4.y+20} ${element7.x},${element2.y+20} ${element3.x},${element6.y+20} ${element4.x},${element5.y+20} ${element4.x},${element1.y+20} ${element2.x},${element5.y+20}" class="white3" id="e"/></svg>`

You can see from this that I also used y+15, y+10 and y+20 as well as x+5 for some of the coordinate pairs. This was because I didn’t want each polygon to line up exactly. Having each coordinate off by 10, 15 or 20, gave it just a touch more randomness, which is what I was going for. You’ll also notice that I added another template expression to define the stroke in an inline style. If you go back to the site, you’ll notice that the strokes for each polygon vary constantly in thickness. This is because I have the stroke defined as a percentage of each elements x coordinate. Here’s how I defined that to use in my polygon inline style:

let stroke1 = element2.x / 75;

let stroke2 = element4.x / 75;

let stroke3 = element7.x / 75;

let stroke4 = element3.x / 75;

For my purposes, the element coordinate divided by 75 gave me just about the thickness I wanted. The thickness never gets too thick, and never completely disappears either. But you can adjust it if necessary. I’m also using a different element for each stroke variable, so that each polygon will have a slightly differently changing stroke.

Then I wrap up my setinterval function and write to the innerHTML of my polygon div. What this does is refresh and update the DOM every 20 milliseconds with the new polygon based on the new coordinates. This might seem excessive. But whatever. It’s really just a splash page, so I’m not too worried about it. Here’s how the entire bit of code looks:

setInterval(function(){

let element1 = site1.getBoundingClientRect();

let element2 = site2.getBoundingClientRect();

let element3 = site3.getBoundingClientRect();

let element4 = site4.getBoundingClientRect();

let element5 = site5.getBoundingClientRect();

let element6 = site6.getBoundingClientRect();

let element7 = site7.getBoundingClientRect();

let stroke1 = element2.x / 75;

let stroke2 = element4.x / 75;

let stroke3 = element7.x / 75;

let stroke4 = element3.x / 75;

let polygon1 = `<svg class="polygonContainer" ><polygon style="stroke:black;stroke-width:${stroke1}" points="${element1.x},${element7.y} ${element2.x},${element6.y} ${element3.x},${element5.y} ${element1.x},${element4.y} ${element2.x},${element5.y} ${element3.x},${element6.y} ${element4.x},${element7.y}" class="white" id="c"/>

<polygon style="stroke:lightgray;stroke-width:${stroke2}" points="${element1.x+5},${element3.y+15} ${element6.x+5},${element3.y+15} ${element7.x+5},${element3.y+15} ${element1.x+5},${element4.y+15} ${element6.x+5},${element5.y+15} ${element5.x+5},${element2.y+15} ${element1.x+5},${element4.y+15}" class="white4" id="f"/>

<polygon style="stroke:gray;stroke-width:${stroke3}" points="${element7.x},${element7.y+10} ${element1.x},${element1.y+10} ${element4.x},${element4.y+10} ${element5.x},${element5.y+10} ${element6.x},${element6.y+10} ${element3.x},${element3.y+10} ${element1.x},${element1.y+10}" class="white2" id="d"/>

<polygon style="stroke:darkgray;stroke-width:${stroke4}" points="${element3.x},${element1.y+20} ${element3.x},${element4.y+20} ${element7.x},${element2.y+20} ${element3.x},${element6.y+20} ${element4.x},${element5.y+20} ${element4.x},${element1.y+20} ${element2.x},${element5.y+20}" class="white3" id="e"/></svg>`document.getElementById('polygon').innerHTML = polygon1;}, 20);

The final step is to go back into CSS and define some styles that I didn’t want to do in the inline styles for each polygon. Initially I had a slight fill for each polygon, which was a cool effect, but massively slowed down the browser as it tries to refresh every 20 milliseconds.

polygon#c.white {width: 100vw;height: 100vh;fill: transparent;overflow: visible;background-color: transparent;}

polygon#d.white2 {width: 100vw;height: 100vh;fill: transparent;overflow: visible;background-color: transparent;}

polygon#e.white3 {width: 100vw;height: 100vh;fill: transparent;overflow: visible;background-color: transparent;}

polygon#f.white4 {width: 100vw;height: 100vh;fill: transparent;overflow: visible;background-color: transparent;}

Each link is clickable and goes to a little bio about each project, and my github for each, blah, blah, blah, blah. This was all just back end database I set up in Node.js to load each of the files onclick. But that part is far less interesting, so I won’t get into it. If you have any questions, or suggestions, I would love to hear it. Thanks!


Published by HackerNoon on 2018/01/06