Creative Portfolio Showcase Design

Creative work/project portfolio page design using HTML, CSS and JS

 Yogesh    24 Dec, 2020    522


 Demo  Download

The creative design of your portfolio page emphasis your creative work. A good portfolio page design emphasis your work & creativity to your clients. In this post, we will design the work/project portfolio page using HTML, CSS, JS, and Anime.js javascript engine to animate the elements.

**List of the elements, we will design and animate in this snippet. **

  • Body background
  • Portfolio Slider
    • Project title
    • Subtitle
    • nav
  • Background SVG shape

Steps

  • Design Portfolio Layout
  • Slider Animation
  • Animate Texts
  • SVG Background Animation
  • Source Code

Design Portfolio Layout

In this section, we will create a basic HTML layout. HTML Markup

<div class="portfolio">
    <div class="portfolio-content" style="--bg: cornsilk" id="portfolio">
       <!-- Portfolio Slider -->
    </div>
</div>

In the above code snippet, we've our core layout. The style="--bg: #colorcode" atribute will used to paint the background color dynamically.

* .portfolio {
    background: var(--bg);
}

CSS

.portfolio {
  transition: all 1.5s;
  background: var(--bg);
}
.portfolio-content {
  position: relative;
  height: 100vh;
  width: 100vw;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  overflow-x: hidden;
  transition: all 1.5s;
  z-index: 1;
}

Portfolio Slider

HTML Structure

<div class="portfolio" style="--bg: cornsilk" id="portfolio">
.
.
<div class="portfolio-item" dd-slide="1" dd-background="cornsilk" dd-gradient='{ "c1": "#ff830066", "c2": "#9400D366" }'>
<!-- Slider Content -->
</div>
.
.

In the above code snippet, we've designed the slider with content.

The dd-slide="slide_number" attribute will used to animate the next & privious slide from the current slide number.

The dd-background="#color" attribute will used to update the portfolio background color.

The dd-gradient="c1 & c2 color" attribute will used to paint the SVG background color.

All the attributes are used to update their perspective values dynamically to animate the slider.

CSS

.portfolio-item {
  position: absolute;
  display: flex;
  height: 50vh;
  min-height: var(--item-height);
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  opacity: 0;
}
.portfolio-item.active {
  position: relative;
  opacity: 1;
  z-index: 9;
}

Slider Content and Animation

In the content section, we do have a title, subtitle & explore button. HTML Structure

<div class="portfolio-item" dd-slide="1" dd-background="cornsilk" dd-gradient='{ "c1": "#ff830066", "c2": "#9400D366" }'>
  <h4 dd-split class="portfolio-subtitle dd-animate__subtitle">design & art</h4>
  <div class='clearfix'></div><br />
  <h1 dd-split class="portfolio-title dd-animate__title">Artisan<br />Craft & Design</h1>
  <div class='clearfix'></div><br />
  <a href="javascript: void(0)" class="portfolio-btn dd-animate"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-narrow-right" width="44" height="44" viewBox="0 0 24 24" stroke-width="1" stroke="#000" fill="none" stroke-linecap="round" stroke-linejoin="round">
    <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
    <line x1="5" y1="12" x2="19" y2="12" />
    <line x1="15" y1="16" x2="19" y2="12" />
    <line x1="15" y1="8" x2="19" y2="12" />
  </svg><span dd-split class="portfolio-btn__text dd-animate__span">Explore</span></a>
</div>

CSS & Animation

/*Project title*/
.portfolio-title {
  position: relative;
  margin: 0;
  font-family: var(--font);
  font-weight: 500;
  color: var(--clr-dark);
  letter-spacing: 1px;
  font-size: 7rem;
}
/* Project subtitle */
.portfolio-subtitle {
  position: absolute;
  display: inline-block;
  top: -50px;
  font-family: var(--font);
  font-weight: 400;
  font-size: 1.5rem;
}
.portfolio-btn {
  position: absolute;
  cursor: pointer;
  padding: 0;
  background: transparent;
  border: 0;
  bottom: -50px;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  font-family: var(--font);
  font-weight: 600;
  color: var(--clr-dark);
  font-size: 1rem;
  outline: none;
  transform: translateX(-1.5rem);
  opacity: 0;
  transition: all 1s;
}

Animation CSS

/* Animations */
/* Project title animation */
.dd-animate__title span {
  opacity: 0;
  display: inline-block;
  transform: translateY(2.5rem) scale(.75);
}
.portfolio-item.active .dd-animate__title span {
  transition: var(--transition);
  transition-delay: var(--delay);
  opacity: 1;
  transform: translateY(0) scale(1);
}
/* Project subtitle animation */
.dd-animate__subtitle span {
  display: inline-block;
  transform: translateX(20px);
  transition: var(--transition);
  transition-delay: var(--delay) !important;
  opacity: 0;
}
.portfolio-item.active .dd-animate__subtitle span {
  transform: translateX(0);
  opacity: 1;
}
/* Project subtitle underline */
.dd-animate__subtitle::after {
  position: absolute; 
  content: "";
  top: calc(50% + 4px);
  left: 0;
  width: 0;
  height: 2px;
  background: var(--clr-dark);
  transform-origin: right;
  transition-delay: .75s !important;
  transition: all 1s;
}
/* Project subtitle underline animation */
.portfolio-item.active .dd-animate__subtitle::after {
  width: 100px;
  left: -120px;
}
/* Char / letters hover animation */
.dd-animate__span span {
  position: relative;
  display: inline-block;
  transform: translateY(5px) scale(.75);
  transition: all .75s;
  opacity: 0;
  transition-delay: var(--delay);
  letter-spacing: 2px;
}
/* Explore btn hover animation */
.dd-animate:hover .dd-animate__span span {
  opacity: 1;
  transform: translateY(0) scale(1);
}

Letter / Character Animation

For the leeter/char animation we have to first split the string into char list. The below js function will split the string and add the required attributes.

let item_list = document.querySelectorAll('.portfolio-item');
// Total items
let total_item = item_list.length;
// Fetch all animated texts
let dd_splits = document.querySelectorAll('[dd-split]');
// Split and animate every letters
dd_splits.forEach(str => {
  let span = '';
  // split string into chars
  let chars = str.innerText.split('');
  let reverse = false;
  let default_delay = .075;
  let default_duration = 1;
  // Duration: check if durations is set or not (if not then set the default value)
  let duration = str.attributes['dd-duration'] ? str.attributes['dd-duration'].value.toFixed(2) : default_duration;
  // Delay: check if delay is set or not (if not then set the default value)
  let delay = str.attributes['dd-delay'] ? str.attributes['dd-delay'].value.toFixed(2) : default_delay;
  // Delay between two transition
  let transition_delay = 0;
  // Total length
  let char_len = chars.length;
  // If transition is reverse
  if( str.attributes['dd-delay-reverse'] ) {
    reverse = true;
    transition_delay = char_len / 10;
  }
  else reverse = false;
  // Create span node, set transition & delay attributes dynamically and append it to span var
  chars.forEach((ch, key) => {
    if(ch == '\n' ) {
      span += '<div class="clearfix"></div>';
    } else if( ch == ' ' ) {
      span += ' ';
    } else {
      span += '<span style="--delay: '+transition_delay+'s" dd-char="'+(ch)+'">'+ch+'</span>';
    }
    str.style = '--transition: '+(duration)+'s';
    if( reverse ) transition_delay = transition_delay - default_delay;
    else transition_delay += Number(delay);
  })
  // Replace the text content with converted span
  str.innerHTML = span;
});

Slider & Background SVG Shapes

After creating the content we will add the previous & next arrows to animate the slide. HTML structure

<div class="portfolio-nav prev" id="prev" dd-slide>
  <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-left" width="38" height="38" viewBox="0 0 24 24" stroke-width="1" stroke="#000000" fill="none" stroke-linecap="round" stroke-linejoin="round">
    <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
    <line x1="5" y1="12" x2="19" y2="12" />
    <line x1="5" y1="12" x2="11" y2="18" />
    <line x1="5" y1="12" x2="11" y2="6" />
  </svg>
  <span dd-split>Prev</span>
</div>

CSS

.portfolio-nav {
  position: absolute;
  display: inline-flex;
  align-items: center;
  font-family: var(--font);
  font-weight: 500;
  font-size: 1.5rem;
  transition: all .5s;
  cursor: pointer;
  top: 50%;
  transform: translateY(-50%);
  z-index: 9;
}
.portfolio-nav.prev {
  left: 100px;
}
.portfolio-nav.next {
  right: 100px;
}

Background SVG Shape

HTML Structure

<div class="portfolio-content">
  <!-- Background Animated Shape -->
  <svg class="background-shape" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
    <!-- Background gradient -->
    <linearGradient id="gradient1" x1="0" x2="0" y1="0" y2="1">
      <stop id="stop1" offset="0%" stop-color="#ff830066"/>
      <stop id="stop2" offset="100%" stop-color="#9400D366"/>
    </linearGradient>
    <path id="svg_path" fill-rule="evenodd" clip-rule="evenodd" d="M52.5616 1.05626C64.6583 0.513218 78.5175 -2.5156 87.7611 5.03785C96.9478 12.5447 95.4872 26.2692 97.4469 37.7343C99.0839 47.3122 101.849 57.0875 98.2039 66.1344C94.6629 74.9234 85.85 80.0148 78.0998 85.7542C70.1123 91.6693 62.6066 99.3013 52.5616 99.9349C42.2547 100.585 31.64 96.3227 24.4782 89.1365C17.8244 82.46 19.8838 71.7615 16.0282 63.2768C11.4887 53.2869 -0.686653 46.2228 0.0303648 35.3356C0.775686 24.0187 9.93179 14.4447 19.6625 8.09493C29.2022 1.86977 41.0599 1.5726 52.5616 1.05626Z" fill="url(#gradient1)"/>
  </svg>
  .
  .
  </div>

CSS

/* Background SVG Shape */
.background-shape {
  position: absolute;
  width: var(--svg-shape-width);
  height: var(--svg-shape-height);
  z-index: -1;
}

Next, we will add the javascript event to animate the slider.

Slider(next/prev) Javascript Event & Bg Shape Animation

let slide_number;
// Next slide
next.addEventListener('click', function() {
  slide_number = ( current_slide == total_item ? 1 : current_slide + 1);
  // Update slide
  updateSlide(slide_number);
  // Update svg background color
  updateSvgBackground(slide_number);
});
// Previous slide
prev.addEventListener('click', function() {
  slide_number = ( current_slide == 1 ? total_item : current_slide - 1);
  // Update slide
  updateSlide(slide_number);
  // Update svg background color
  updateSvgBackground(slide_number);
});

// Animate svg shape
function updateSlide(slide_number) {
  // SVG path set(array list)
  const paths = [
  'M52.5616 1.05626C64.6583 0.513218 78.5175 -2.5156 87.7611 5.03785C96.9478 12.5447 95.4872 26.2692 97.4469 37.7343C99.0839 47.3122 101.849 57.0875 98.2039 66.1344C94.6629 74.9234 85.85 80.0148 78.0998 85.7542C70.1123 91.6693 62.6066 99.3013 52.5616 99.9349C42.2547 100.585 31.64 96.3227 24.4782 89.1365C17.8244 82.46 19.8838 71.7615 16.0282 63.2768C11.4887 53.2869 -0.686653 46.2228 0.0303648 35.3356C0.775686 24.0187 9.93179 14.4447 19.6625 8.09493C29.2022 1.86977 41.0599 1.5726 52.5616 1.05626Z',
  'M47.6845 0.820792C57.8312 2.21574 65.9096 9.5486 74.5227 15.7811C83.7903 22.4873 97.4811 26.2741 99.7139 38.4698C101.975 50.8225 90.2594 60.3113 84.6735 71.226C80.4944 79.3917 78.0658 88.9225 71.1758 94.2321C64.3994 99.4543 55.8437 100.615 47.6845 99.7309C40.0806 98.9069 34.1505 93.1749 27.2472 89.4716C18.235 84.637 5.34967 84.9763 1.1178 74.7503C-3.06639 64.6394 5.67406 53.8657 8.02464 43.0215C10.6411 30.9508 7.65585 16.1987 15.6542 7.68797C23.7721 -0.950018 36.6267 -0.699423 47.6845 0.820792Z',
  'M49.8483 0.603104C57.7254 1.34548 64.7302 4.5352 72.0188 7.92353C81.8333 12.4861 96.9533 12.3867 99.6864 23.772C102.468 35.3619 85.905 42.058 82.9848 53.6062C79.8306 66.0801 89.8659 81.3935 82.5574 91.5114C75.4119 101.404 61.2672 100.322 49.8483 99.4802C39.0108 98.6811 28.8724 94.0877 20.1597 86.9108C11.5294 79.8017 3.73608 70.769 1.01119 59.2515C-1.63467 48.068 1.25521 36.2787 5.70651 25.8202C9.74851 16.3234 16.3239 8.28436 24.7531 3.46895C32.4131 -0.906989 41.2589 -0.206403 49.8483 0.603104Z',
  'M50.3391 0.0424737C61.472 0.903899 63.5849 15.9442 72.3722 22.3011C80.6051 28.2569 96.019 26.4284 99.4213 35.5492C102.811 44.6355 90.3541 52.1243 86.8758 61.182C83.5399 69.8686 86.1043 80.3301 79.5514 87.2906C72.3191 94.9726 61.3726 99.7318 50.3391 99.9908C39.1686 100.253 28.96 94.8708 20.0251 88.6879C11.2105 82.5883 1.67897 75.3267 0.158963 65.2666C-1.30862 55.5534 7.74548 47.6883 12.5051 38.9167C16.4015 31.7358 19.6256 24.6469 25.4428 18.6699C32.7589 11.1526 39.4192 -0.802475 50.3391 0.0424737Z'
  ];
  // SVG Shape animation using Anime js.
  let timeline = anime({
    duration: 1250,
    targets: '#svg_path',
    d: [{
      value: paths[slide_number - 1]
    }],
    easing: 'easeOutCubic',
  });
  // Fetch current slide from slide_number
  let current_item = document.querySelector('[dd-slide="'+slide_number+'"]');
  item_list.forEach(i => {
    // Reset default activated slide
    i.classList.remove('active');
    // Activate the current slide
    current_item.classList.add('active');
  });
  current_slide = slide_number;
}
// Animate svg shape color(gradient)
function updateSvgBackground(slide_number) {
  let item = document.querySelector('[dd-slide="'+slide_number+'"]');
  let bg_value = item.attributes['dd-background'].value;
  portfolio.attributes['style'].value = '--bg: '+bg_value;
  let gradient_set = item.attributes['dd-gradient'].value;
  let color1 = JSON.parse(gradient_set).c1;
  let color2 = JSON.parse(gradient_set).c2;
  stop1.attributes['stop-color'].value = color1;
  stop2.attributes['stop-color'].value = color2;
}

You can also run the slider by pressing the left & right arrow keys. Add the below code for arrow key animation.

// Arrow key(left & right arrow) function to animate the slider.
document.onkeydown = e => {
  if(e.keyCode == 37) prev.click();
  else if(e.keyCode == 39) next.click();
}

Now call the init() funtion to initialize the first slide.

// Init function
function init() {
  // Select item 1
  let item_1 = document.querySelector('[dd-slide="1"]');
  item_1.classList.add('active');
}
// Init
document.onload = init();

Combined together=>

Source Code

HTML

<div class="portfolio" style="--bg: cornsilk" id="portfolio">
  <!-- Portfolio Slider -->
  <div class="portfolio-content">
    <!-- Background Animated Shape -->
    <svg class="background-shape" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
      <!-- Background gradient -->
      <linearGradient id="gradient1" x1="0" x2="0" y1="0" y2="1">
        <stop id="stop1" offset="0%" stop-color="#ff830066"/>
        <stop id="stop2" offset="100%" stop-color="#9400D366"/>
      </linearGradient>
      <path id="svg_path" fill-rule="evenodd" clip-rule="evenodd" d="M52.5616 1.05626C64.6583 0.513218 78.5175 -2.5156 87.7611 5.03785C96.9478 12.5447 95.4872 26.2692 97.4469 37.7343C99.0839 47.3122 101.849 57.0875 98.2039 66.1344C94.6629 74.9234 85.85 80.0148 78.0998 85.7542C70.1123 91.6693 62.6066 99.3013 52.5616 99.9349C42.2547 100.585 31.64 96.3227 24.4782 89.1365C17.8244 82.46 19.8838 71.7615 16.0282 63.2768C11.4887 53.2869 -0.686653 46.2228 0.0303648 35.3356C0.775686 24.0187 9.93179 14.4447 19.6625 8.09493C29.2022 1.86977 41.0599 1.5726 52.5616 1.05626Z" fill="url(#gradient1)"/>
    </svg>
    <!-- Slide 1 -->
    <div class="portfolio-item" dd-slide="1" dd-background="cornsilk" dd-gradient='{ "c1": "#ff830066", "c2": "#9400D366" }'>
      <h4 dd-split class="portfolio-subtitle dd-animate__subtitle">design & art</h4>
      <div class='clearfix'></div><br />
      <h1 dd-split class="portfolio-title dd-animate__title">Artisan<br />Craft & Design</h1>
      <div class='clearfix'></div><br />
      <a href="javascript: void(0)" class="portfolio-btn dd-animate"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-narrow-right" width="44" height="44" viewBox="0 0 24 24" stroke-width="1" stroke="#000" fill="none" stroke-linecap="round" stroke-linejoin="round">
        <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
        <line x1="5" y1="12" x2="19" y2="12" />
        <line x1="15" y1="16" x2="19" y2="12" />
        <line x1="15" y1="8" x2="19" y2="12" />
      </svg><span dd-split class="portfolio-btn__text dd-animate__span">Explore</span></a>
    </div>
    <!-- Slide 2 -->
    <div class="portfolio-item" dd-slide="2" dd-background="#ffc0cb99" dd-gradient='{"c1": "#537AE366", "c2": "#2F817966"}'>
      <h4 dd-split class="portfolio-subtitle dd-animate__subtitle">graphics & illustration</h4>
      <div class="clearfix"></div>
      <h1 dd-split class="portfolio-title dd-animate__title">Abc<br />Sports</h1>
      <a href="javascript: void(0)" class="portfolio-btn dd-animate"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-narrow-right" width="44" height="44" viewBox="0 0 24 24" stroke-width="1" stroke="#000" fill="none" stroke-linecap="round" stroke-linejoin="round">
        <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
        <line x1="5" y1="12" x2="19" y2="12" />
        <line x1="15" y1="16" x2="19" y2="12" />
        <line x1="15" y1="8" x2="19" y2="12" />
      </svg><span dd-split class="portfolio-btn__text dd-animate__span">Explore</span></a>
    </div>
    <!-- Slide 3 -->
    <div class="portfolio-item" dd-slide="3" dd-background="#8BD3B922" dd-gradient='{"c1": "#F5AD1D66", "c2": "#21414466"}'>
      <h4 dd-split class="portfolio-subtitle dd-animate__subtitle">website & app</h4>
      <div class="clearfix"></div>
      <h1 dd-split class="portfolio-title dd-animate__title">Z Beauty<br />& Spa</h1>
      <a href="javascript: void(0)" class="portfolio-btn dd-animate"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-narrow-right" width="44" height="44" viewBox="0 0 24 24" stroke-width="1" stroke="#000" fill="none" stroke-linecap="round" stroke-linejoin="round">
        <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
        <line x1="5" y1="12" x2="19" y2="12" />
        <line x1="15" y1="16" x2="19" y2="12" />
        <line x1="15" y1="8" x2="19" y2="12" />
      </svg><span dd-split class="portfolio-btn__text dd-animate__span">Explore</span></a>
    </div>
    <!-- Slide 4 -->
    <div class="portfolio-item" dd-slide="4" dd-background="#8b000033" dd-gradient='{"c1": "#ed6ea0", "c2": "#ec8c69"}'>
      <h4 dd-split class="portfolio-subtitle dd-animate__subtitle">website design</h4>
      <div class="clearfix"></div>
      <h1 dd-split class="portfolio-title dd-animate__title">Admin Theme</h1>
      <a href="javascript: void(0)" class="portfolio-btn dd-animate"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-narrow-right" width="44" height="44" viewBox="0 0 24 24" stroke-width="1" stroke="#000" fill="none" stroke-linecap="round" stroke-linejoin="round">
        <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
        <line x1="5" y1="12" x2="19" y2="12" />
        <line x1="15" y1="16" x2="19" y2="12" />
        <line x1="15" y1="8" x2="19" y2="12" />
      </svg><span dd-split class="portfolio-btn__text dd-animate__span">Explore</span></a>
    </div>
  </div>
  <!-- Slider Nav -->
  <!-- Btn:Previous -->
  <div class="portfolio-nav prev dd-animate" id="prev" dd-slide>
    <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-left" width="38" height="38" viewBox="0 0 24 24" stroke-width="1" stroke="#000000" fill="none" stroke-linecap="round" stroke-linejoin="round">
      <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
      <line x1="5" y1="12" x2="19" y2="12" />
      <line x1="5" y1="12" x2="11" y2="18" />
      <line x1="5" y1="12" x2="11" y2="6" />
    </svg>
    <span class="dd-animate__span" dd-split>Prev</span>
  </div>
  <!-- Btn:Next -->
  <div class="portfolio-nav next dd-animate" id="next" dd-slide>
    <span class="dd-animate__span" dd-split dd-delay-reverse>Next</span>
    <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-right" width="38" height="38" viewBox="0 0 24 24" stroke-width="1" stroke="#000000" fill="none" stroke-linecap="round" stroke-linejoin="round">
      <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
      <line x1="5" y1="12" x2="19" y2="12" />
      <line x1="13" y1="18" x2="19" y2="12" />
      <line x1="13" y1="6" x2="19" y2="12" />
    </svg>
  </div>
</div>

CSS

/* CSS variables */
:root {
  --clr-dark: #121212;
  --bg: cornsilk;
  --font: 'Playfair Display', serif;
  --item-height: 500px;
  --svg-shape-width: 400px;
  --svg-shape-height: 400px;
}
/* Reset default browser properties. */
body {
  margin: 0;
  padding: 0;
}
.clearfix::after {
  content: "";
  clear: both;
  display: table;
}
.portfolio {
  transition: all 1.5s;
  background: var(--bg);
}
.portfolio-content {
  position: relative;
  height: 100vh;
  width: 100vw;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  overflow-x: hidden;
  transition: all 1.5s;
  z-index: 1;
}
.portfolio-item {
  position: absolute;
  display: flex;
  height: 50vh;
  min-height: var(--item-height);
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  opacity: 0;
}
.portfolio-item.active {
  position: relative;
  opacity: 1;
  z-index: 9;
}
/*Project title*/
.portfolio-title {
  position: relative;
  margin: 0;
  font-family: var(--font);
  font-weight: 500;
  color: var(--clr-dark);
  letter-spacing: 1px;
  font-size: 7rem;
}
/* Project subtitle */
.portfolio-subtitle {
  position: absolute;
  display: inline-block;
  top: -50px;
  font-family: var(--font);
  font-weight: 400;
  font-size: 1.5rem;
}
/* Explore button */
.portfolio-btn {
  position: absolute;
  cursor: pointer;
  padding: 0;
  background: transparent;
  border: 0;
  bottom: -50px;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  font-family: var(--font);
  font-weight: 600;
  color: var(--clr-dark);
  font-size: 1rem;
  outline: none;
  transform: translateX(-1.5rem);
  opacity: 0;
  transition: all 1s;
}
.portfolio-item.active .portfolio-btn {
  opacity: 1;
  transform: translateX(0);
}
/* Background SVG Shape */
.background-shape {
  position: absolute;
  width: var(--svg-shape-width);
  height: var(--svg-shape-height);
  z-index: -1;
}
/* Project slider nav */
/* Prev / Next */
.portfolio-nav {
  position: absolute;
  display: inline-flex;
  align-items: center;
  font-family: var(--font);
  font-weight: 500;
  font-size: 1.5rem;
  transition: all .5s;
  cursor: pointer;
  top: 50%;
  transform: translateY(-50%);
  z-index: 9;
}
.portfolio-nav.prev {
  left: 100px;
}
.portfolio-nav.next {
  right: 100px;
}
/* Animations */
/* Project title animation */
.dd-animate__title span {
  opacity: 0;
  display: inline-block;
  transform: translateY(2.5rem) scale(.75);
}
.portfolio-item.active .dd-animate__title span {
  transition: var(--transition);
  transition-delay: var(--delay);
  opacity: 1;
  transform: translateY(0) scale(1);
}
/* Project subtitle animation */
.dd-animate__subtitle span {
  display: inline-block;
  transform: translateX(20px);
  transition: var(--transition);
  transition-delay: var(--delay) !important;
  opacity: 0;
}
.portfolio-item.active .dd-animate__subtitle span {
  transform: translateX(0);
  opacity: 1;
}
/* Project subtitle underline */
.dd-animate__subtitle::after {
  position: absolute; 
  content: "";
  top: calc(50% + 4px);
  left: 0;
  width: 0;
  height: 2px;
  background: var(--clr-dark);
  transform-origin: right;
  transition-delay: .75s !important;
  transition: all 1s;
}
/* Project subtitle underline animation */
.portfolio-item.active .dd-animate__subtitle::after {
  width: 100px;
  left: -120px;
}
/* Char / letters hover animation */
.dd-animate__span span {
  position: relative;
  display: inline-block;
  transform: translateY(5px) scale(.75);
  transition: all .75s;
  opacity: 0;
  transition-delay: var(--delay);
  letter-spacing: 2px;
}
/* Explore btn hover animation */
.dd-animate:hover .dd-animate__span span {
  opacity: 1;
  transform: translateY(0) scale(1);
}
/* Media query */
@media (max-width: 768px) {
  /* Project items */
  .portfolio-item {
    height: 70vh;
    min-height: auto;
  }
  .portfolio-title {
    font-size: 3rem;
  }
  /* SVG shape */
  .background-shape {
    width: 75%;
    height: auto;
  }
  /* Nav */
  .portfolio-nav {
    top: 100px;
  }
  .portfolio-nav.prev {
    left: 0;
  }
  .portfolio-nav.next {
    right: 0;
  }
}

Javascript

<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/2.2.0/anime.min.js"></script> 
<script>
  let current_slide = 1;
  // Fetch all itmes
  let item_list = document.querySelectorAll('.portfolio-item');
  // Total items
  let total_item = item_list.length;
  // Fetch all animated texts
  let dd_splits = document.querySelectorAll('[dd-split]');
  // Split and animate every letters
  dd_splits.forEach(str => {
    let span = '';
    // split string into chars
    let chars = str.innerText.split('');
    let reverse = false;
    let default_delay = .075;
    let default_duration = 1;
    // Duration: check if durations is set or not (if not then set the default value)
    let duration = str.attributes['dd-duration'] ? str.attributes['dd-duration'].value.toFixed(2) : default_duration;
    // Delay: check if delay is set or not (if not then set the default value)
    let delay = str.attributes['dd-delay'] ? str.attributes['dd-delay'].value.toFixed(2) : default_delay;
    // Delay between two transition
    let transition_delay = 0;
    // Total length
    let char_len = chars.length;
    // If transition is reverse
    if( str.attributes['dd-delay-reverse'] ) {
      reverse = true;
      transition_delay = char_len / 10;
    }
    else reverse = false;
    // Create span node, set transition & delay attributes dynamically and append it to span var
    chars.forEach((ch, key) => {
      if(ch == '\n' ) {
        span += '<div class="clearfix"></div>';
      } else if( ch == ' ' ) {
        span += ' ';
      } else {
        span += '<span style="--delay: '+transition_delay+'s" dd-char="'+(ch)+'">'+ch+'</span>';
      }
      str.style = '--transition: '+(duration)+'s';
      if( reverse ) transition_delay = transition_delay - default_delay;
      else transition_delay += Number(delay);
    })
    // Replace the text content with converted span
    str.innerHTML = span;
  });
  let slide_number;
  // Next slide
  next.addEventListener('click', function() {
    slide_number = ( current_slide == total_item ? 1 : current_slide + 1);
    // Update slide
    updateSlide(slide_number);
    // Update svg background color
    updateSvgBackground(slide_number);
  });
  // Previous slide
  prev.addEventListener('click', function() {
    slide_number = ( current_slide == 1 ? total_item : current_slide - 1);
    // Update slide
    updateSlide(slide_number);
    // Update svg background color
    updateSvgBackground(slide_number);
  });
  // Update & Animate svg shape
  function updateSlide(slide_number) {
    // SVG path set(array list)
    const paths = [
    'M52.5616 1.05626C64.6583 0.513218 78.5175 -2.5156 87.7611 5.03785C96.9478 12.5447 95.4872 26.2692 97.4469 37.7343C99.0839 47.3122 101.849 57.0875 98.2039 66.1344C94.6629 74.9234 85.85 80.0148 78.0998 85.7542C70.1123 91.6693 62.6066 99.3013 52.5616 99.9349C42.2547 100.585 31.64 96.3227 24.4782 89.1365C17.8244 82.46 19.8838 71.7615 16.0282 63.2768C11.4887 53.2869 -0.686653 46.2228 0.0303648 35.3356C0.775686 24.0187 9.93179 14.4447 19.6625 8.09493C29.2022 1.86977 41.0599 1.5726 52.5616 1.05626Z',
    'M47.6845 0.820792C57.8312 2.21574 65.9096 9.5486 74.5227 15.7811C83.7903 22.4873 97.4811 26.2741 99.7139 38.4698C101.975 50.8225 90.2594 60.3113 84.6735 71.226C80.4944 79.3917 78.0658 88.9225 71.1758 94.2321C64.3994 99.4543 55.8437 100.615 47.6845 99.7309C40.0806 98.9069 34.1505 93.1749 27.2472 89.4716C18.235 84.637 5.34967 84.9763 1.1178 74.7503C-3.06639 64.6394 5.67406 53.8657 8.02464 43.0215C10.6411 30.9508 7.65585 16.1987 15.6542 7.68797C23.7721 -0.950018 36.6267 -0.699423 47.6845 0.820792Z',
    'M49.8483 0.603104C57.7254 1.34548 64.7302 4.5352 72.0188 7.92353C81.8333 12.4861 96.9533 12.3867 99.6864 23.772C102.468 35.3619 85.905 42.058 82.9848 53.6062C79.8306 66.0801 89.8659 81.3935 82.5574 91.5114C75.4119 101.404 61.2672 100.322 49.8483 99.4802C39.0108 98.6811 28.8724 94.0877 20.1597 86.9108C11.5294 79.8017 3.73608 70.769 1.01119 59.2515C-1.63467 48.068 1.25521 36.2787 5.70651 25.8202C9.74851 16.3234 16.3239 8.28436 24.7531 3.46895C32.4131 -0.906989 41.2589 -0.206403 49.8483 0.603104Z',
    'M50.3391 0.0424737C61.472 0.903899 63.5849 15.9442 72.3722 22.3011C80.6051 28.2569 96.019 26.4284 99.4213 35.5492C102.811 44.6355 90.3541 52.1243 86.8758 61.182C83.5399 69.8686 86.1043 80.3301 79.5514 87.2906C72.3191 94.9726 61.3726 99.7318 50.3391 99.9908C39.1686 100.253 28.96 94.8708 20.0251 88.6879C11.2105 82.5883 1.67897 75.3267 0.158963 65.2666C-1.30862 55.5534 7.74548 47.6883 12.5051 38.9167C16.4015 31.7358 19.6256 24.6469 25.4428 18.6699C32.7589 11.1526 39.4192 -0.802475 50.3391 0.0424737Z'
    ];
    // Shape animation using Anime js.
    let timeline = anime({
      duration: 1250,
      targets: '#svg_path',
      d: [{
        value: paths[slide_number - 1]
      }],
      easing: 'easeOutCubic',
    });
    // Fetch current slide from slide_number
    let current_item = document.querySelector('[dd-slide="'+slide_number+'"]');
    item_list.forEach(i => {
      // Reset default activated slide
      i.classList.remove('active');
      // Activate the current slide
      current_item.classList.add('active');
    });
    current_slide = slide_number;
  }
  // Animate svg shape color
  function updateSvgBackground(slide_number) {
    let item = document.querySelector('[dd-slide="'+slide_number+'"]');
    let bg_value = item.attributes['dd-background'].value;
    portfolio.attributes['style'].value = '--bg: '+bg_value;
    let gradient_set = item.attributes['dd-gradient'].value;
    let color1 = JSON.parse(gradient_set).c1;
    let color2 = JSON.parse(gradient_set).c2;
    stop1.attributes['stop-color'].value = color1;
    stop2.attributes['stop-color'].value = color2;
  }
  // Arrow key(left & right arrow) function to animate the slider.
  document.onkeydown = e => {
    if(e.keyCode == 37) prev.click();
    else if(e.keyCode == 39) next.click();
  }
  // Init function
  function init() {
    // Select item 1
    let item_1 = document.querySelector('[dd-slide="1"]');
    item_1.classList.add('active');
  }
  // Init
  document.onload = init();
</script>

I hope you guys find this snippet inspiring.


Credits / Resources

  • Google Font(Playfair Display) => https://fonts.google.com/specimen/Playfair+Display
  • Anime js(javascript animation engine) => https://animejs.com/



Related Snippets