Комбинирование кривых Безье и координат jQuery.

Недавно я работал над новым сайтом-портфолио. У меня в голове была идея, как я хочу, чтобы это выглядело, и мне пришлось попробовать несколько вещей, с которыми я не был знаком, чтобы заставить это работать. Вот сайт, и если вы хотите попробовать что-то подобное, я объясню свой мыслительный процесс о том, как он работал: https://ethansportfolio.herokuapp.com/

Как видите, кнопки-ссылки на каждый из моих сайтов перемещаются по экрану. В качестве поплавка они создают геометрическую фигуру на заднем плане, которая перемещается и корректируется с перемещением плавающих кнопок. Это более или менее то, что я имел в виду, когда начинал.

Мой мыслительный процесс был таким:

  1. Мне нужно было создать элементы HTML.
  2. Мне нужно оживить элементы HTML.
  3. Мне нужно создать элемент многоугольника svg, точки которого основаны на анимированных элементах.

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>

Довольно простой. Теперь я собираюсь создать пустой Div, который будет удерживать мой многоугольник на заднем плане.

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

Сладкий. Пока все выглядит хорошо. Затем мне нужно оживить каждую ссылку. Я попробовал несколько вариантов, чтобы это произошло. Сначала я рассчитывал размер экрана / окна, а затем перемещал элементы в JavaScript на основе текущих размеров окна. Этот проспект не очень удачный. Наконец я наткнулся на статью об использовании кривой Безье. Раньше я использовал кривую Безье при переходах, но никогда не использовал анимацию ключевых кадров в CSS, но это выглядело многообещающе. Легко увидеть некоторые анимации быстро после ввода кода, но для меня, поскольку это был мой первый раз, потребовалось немало изящества, чтобы заставить анимацию выглядеть так, как я ее себе представлял. Поскольку я хотел, чтобы каждый элемент плавал иначе, чем остальные, я создал отдельную анимацию ключевого кадра для каждого элемента и настроил каждый отдельно. Вот код котла, который я использовал для анимации ключевых кадров в 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);
  }
}

Теперь, если у вас есть два или три элемента на экране, которые вы хотите анимировать несколько случайным образом, как это сделал я, довольно легко отредактировать этот код котельной пластины для этого. Обе оси x и y имеют параметр "преобразовать: перевести". Я установил для каждого элемента значение vh или vw. По сути, это означает, что они будут анимироваться до этой точки в окне просмотра, прежде чем вернуться в другом направлении, что-то вроде старых школьных заставок с шарами, которые подпрыгивают по экрану. У каждой анимации также есть время в секундах. Таким образом, поскольку для этого элемента установлено значение 50s и 80vw, это означает, что он будет анимирован примерно до 80% области просмотра за 50 секунд. Если вы создадите второй элемент, вы можете настроить его на 90vw и 20s, и, хотя он довольно похож, анимации будут выглядеть совершенно по-разному по сравнению друг с другом, поскольку они движутся с разной скоростью и имеют немного разные пути. Имейте в виду, я делаю это впервые, поэтому я все еще знакомлюсь со всем этим. С этой отправной точкой вы можете добавить несколько элементов и просто настроить перевод и синхронизацию анимации, чтобы получить несколько путей для вашей анимации.

Координаты

Естественно, теперь, когда у меня есть анимация элементов на экране, моя следующая цель - отслеживать их координаты. Я хочу использовать эти координаты в качестве точек для элемента многоугольника svg. Я нашел несколько способов сделать это в JavaScript, прежде чем наткнулся на эту абсолютно ЗОЛОТУЮ функцию в jquery.

getBoundingClientRect();

Используя это, вы получите не только координаты x и y любого элемента на экране, но также верхний и левый угол, а также высоту и ширину любого элемента. Это очень удобно! Так что я хотел отслеживать каждый из семи моих элементов. Возможно, есть лучший способ сделать это, но я подумал, поскольку они постоянно находятся в движении, я хочу проверять новые координаты каждые несколько миллисекунд. Я обнаружил, что если я установил функцию setinterval на 50 миллисекунд, это было бы слишком прерывисто… как и все, что больше 50 миллисекунд. И все, что меньше 5, должно быть слишком интенсивным. Примерно 20 миллисекунд показались мне волшебным числом. Итак, я начал с создания функции setinterval и внутри нее с помощью getBoundingClientRect (); функция для каждого элемента, которая будет давать мне координаты каждого элемента каждые 20 миллисекунд. Вот как это выглядит:

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();

Теперь я могу в консоли записывать каждый элемент, чтобы убедиться, что получаю координаты. Следующая часть будет выглядеть немного сумасшедшей, но повеселитесь со мной. Поэтому я динамически создаю элемент svg с каждым многоугольником внутри, беру координаты элемента с помощью обратных кавычек и использую их в качестве точек для каждого многоугольника. Обычно это выглядело бы примерно так:

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

И так далее. Но поскольку мне нужно было немного случайности в многоугольнике, я изменил его так, чтобы координаты не совпадали точно для каждой пары координат. Я мог бы использовать element1.x и element7.y в качестве пары координат вместо element1.x и element1.y. Во всяком случае, вот что я использовал:

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

Из этого видно, что я также использовал y + 15, y + 10 и y + 20, а также x + 5 для некоторых пар координат. Это произошло потому, что я не хотел, чтобы каждый многоугольник совпадал точно. Отклонение каждой координаты на 10, 15 или 20 давало немного больше случайности, что я и делал. Вы также заметите, что я добавил еще одно шаблонное выражение для определения обводки во встроенном стиле. Если вы вернетесь на сайт, то заметите, что штрихи для каждого многоугольника постоянно различаются по толщине. Это потому, что у меня обводка определяется как процент от координаты x каждого элемента. Вот как я определил это для использования в моем встроенном стиле многоугольника:

let stroke1 = element2.x / 75;
let stroke2 = element4.x / 75;
let stroke3 = element7.x / 75;
let stroke4 = element3.x / 75;

Для моих целей координата элемента, разделенная на 75, дала мне примерно желаемую толщину. Толщина никогда не становится слишком толстой и никогда не исчезает полностью. Но при необходимости его можно отрегулировать. Я также использую разные элементы для каждой переменной обводки, так что обводка каждого многоугольника будет немного отличаться.

Затем я завершаю свою функцию setinterval и записываю в innerHTML моего многоугольного div. Это обновляет и обновляет DOM каждые 20 миллисекунд с новым многоугольником на основе новых координат. Это может показаться чрезмерным. Но как бы там ни было. На самом деле это просто заставка, поэтому меня это не особо беспокоит. Вот как выглядит весь фрагмент кода:

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);

Последний шаг - вернуться в CSS и определить некоторые стили, которые я не хотел использовать во встроенных стилях для каждого многоугольника. Изначально у меня была небольшая заливка для каждого многоугольника, что было классным эффектом, но сильно замедлило работу браузера, поскольку он пытается обновляться каждые 20 миллисекунд.

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

Каждая ссылка кликабельна и ведет к небольшой биографии о каждом проекте и моему github для каждого, бла, бла, бла, бла. Это была всего лишь серверная база данных, которую я настроил в Node.js для загрузки каждого из файлов onclick. Но эта часть гораздо менее интересна, поэтому я не буду вдаваться в подробности. Если у вас есть какие-либо вопросы или предложения, я хотел бы их услышать. Спасибо!