How I Created My Homepage JavaScript Post Carousel

on in JavaScript Carousels, JavaScript DOM
Last modified on

My homepage now features a JavaScript section of 4 featured posts. Notice the official JavaScript yellow colour? Here’s how I did it.

Note that this is mostly a WordPress feature, so it uses a loop to get all the content (titles, links, excerpts and images), but it can be easily adapted to any CMS or static site. Just remove the loop and replace the JavaScript const arrays.

HTML/PHP/JS Code

Here’s my current HTML/JS structure. I opted for inline JS, as it can be compressed and served immediately, without an additional HTTP request. For readability, I’ll leave the JavaScript unminified.

<?php
$args = [
    'post_status' => 'publish',
    'post_type' => 'post',
    'posts_per_page' => 4,
    'category_name' => 'javascript'
];

$data = '<div id="carousel-wrapper">
    <div id="menu">
        <div id="current-option">
            <a id="current-option-permalink" href=""><span id="current-option-text1" data-previous-text="" data-next-text=""></span></a>
            <span id="current-option-text2" data-previous-text="" data-next-text=""></span>
        </div>
        <div id="image"></div>
    </div>

    <button id="previous-option"></button>
    <button id="next-option"></button>
</div>';

$featuredQuery = new WP_Query($args);

$titles = [];
$excerpts = [];
$permalinks = [];
$illustrations = [];

if ($featuredQuery->have_posts()) {
    while ($featuredQuery->have_posts()) {
        $featuredQuery->the_post();

        $postID = get_the_ID();

        $excerpt = substr(get_the_excerpt(), 0, 140);

        $titles[] = get_the_title($postID);
        $excerpts[] = substr($excerpt, 0, strrpos($excerpt, ' '));
        $permalinks[] = get_permalink($postID);
        $illustrations[] = get_the_post_thumbnail_url($postID, 'medium');
    }
}

$titles = implode(',', array_map('whiskey_add_quotes', $titles));
$excerpts = implode(',', array_map('whiskey_add_quotes', $excerpts));
$permalinks = implode(',', array_map('whiskey_add_quotes', $permalinks));
$illustrations = implode(',', array_map('whiskey_add_quotes', $illustrations));

$data .= '<script>
const text1_options = [' . $titles . '];
const text2_options = [' . $excerpts . '];
const color_options = ["#f7df1e", "#f7df1e", "#f7df1e", "#f7df1e"];
const image_options = [' . $illustrations . '];
const permalink_options = [' . $permalinks . '];

var i = 0;
const currentOptionText1 = document.getElementById("current-option-text1");
const currentOptionText2 = document.getElementById("current-option-text2");
const currentOptionImage = document.getElementById("image");
const currentOptionPermalink = document.getElementById("current-option-permalink");

const carousel = document.getElementById("carousel-wrapper");
const mainMenu = document.getElementById("menu");
const optionPrevious = document.getElementById("previous-option");
const optionNext = document.getElementById("next-option");

currentOptionText1.innerText = text1_options[i];
currentOptionText2.innerText = text2_options[i];
currentOptionImage.style.backgroundImage = "url(" + image_options[i] + ")";
currentOptionPermalink.href = permalink_options[i];
mainMenu.style.background = color_options[i];

optionNext.onclick = function () {
    i = i + 1;
    i = i % text1_options.length;
    currentOptionText1.dataset.nextText = text1_options[i];
    currentOptionText2.dataset.nextText = text2_options[i];

    mainMenu.style.background = color_options[i];
    carousel.classList.add("anim-next");
  
    setTimeout(() => {
        currentOptionImage.style.backgroundImage = "url(" + image_options[i] + ")";
    }, 455);
  
    setTimeout(() => {
        currentOptionText1.innerText = text1_options[i];
        currentOptionText2.innerText = text2_options[i];
        currentOptionPermalink.href = permalink_options[i];
        carousel.classList.remove("anim-next");
    }, 650);
};

optionPrevious.onclick = function () {
    if (i === 0) {
        i = text1_options.length;
    }
    i = i - 1;
    currentOptionText1.dataset.previousText = text1_options[i];
    currentOptionText2.dataset.previousText = text2_options[i];

    mainMenu.style.background = color_options[i];
    carousel.classList.add("anim-previous");

    setTimeout(() => {
        currentOptionImage.style.backgroundImage = "url(" + image_options[i] + ")";
    }, 455);
  
    setTimeout(() => {
        currentOptionText1.innerText = text1_options[i];
        currentOptionText2.innerText = text2_options[i];
        currentOptionPermalink.href = permalink_options[i];
        carousel.classList.remove("anim-previous");
    }, 650);
};
</script>';

return $data;

CSS Code

And here is the CSS.

#carousel-wrapper,
#carousel-wrapper #menu {
    width: 100%;

    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
}

#carousel-wrapper {
    position: relative;
    height: auto;
}
#carousel-wrapper #menu {
    height: 380px;
    overflow: hidden;
    vertical-align: middle;
    box-shadow: 0px 0px 12px 0px rgb(0 0 0 / 20%);
    border-radius: 5px;

    transition: all 0.6s ease-in-out;
}
#carousel-wrapper #menu #current-option {
    position: relative;
    width: 50%;
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    overflow: hidden;
}
#carousel-wrapper #menu #current-option a {
    color: var(--primary_colour);
}

#carousel-wrapper #menu #current-option #current-option-text1,
#carousel-wrapper #menu #current-option #current-option-text2 {
    width: 240px;

    display: flex;
    flex-direction: column;
    align-items: flex-start;
}
#carousel-wrapper #menu #current-option #current-option-text1 {
    font-size: 24px;
    font-weight: 700;
    line-height: 1.35;
    height: 200px;
    justify-content: flex-start;
}
#carousel-wrapper #menu #current-option #current-option-text2 {
    font-size: 0.8rem;
    height: 40px;
    justify-content: flex-end;
}

#carousel-wrapper #menu #current-option #current-option-text1::before,
#carousel-wrapper #menu #current-option #current-option-text1::after {
    position: absolute;
    width: 240px;
    height: 200px;

    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
}
#carousel-wrapper #menu #current-option #current-option-text1::before {
    content: attr(data-next-text);
    transform: translate(0%, 380px);
}
#carousel-wrapper #menu #current-option #current-option-text1::after {
    content: attr(data-previous-text);
    transform: translate(0%, -380px);
}

#carousel-wrapper #menu #current-option #current-option-text2::before,
#carousel-wrapper #menu #current-option #current-option-text2::after {
    position: absolute;
    width: 240px;
    height: 40px;

    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-end;
}
#carousel-wrapper #menu #current-option #current-option-text2::before {
    content: attr(data-next-text);
    transform: translate(0%, 380px);
}
#carousel-wrapper #menu #current-option #current-option-text2::after {
    content: attr(data-previous-text);
    transform: translate(0%, -380px);
}

#carousel-wrapper #previous-option,
#carousel-wrapper #next-option {
    width: 1.5rem;
    height: 1.5rem;
    border: none;
    display: block;
    cursor: pointer;
    background: url("data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 256 256'%3E%3Cpolygon points='225.813,48.907 128,146.72 30.187,48.907 0,79.093 128,207.093 256,79.093' fill='%23333'%3E%3C/polygon%3E%3C/svg%3E");
    background-size: cover;
    position: absolute;
    right: 0;
}
#carousel-wrapper #previous-option {
    transform: scale(1.2) translate(10px, 24px);
}
#carousel-wrapper #next-option {
    transform: scale(1.2) translate(10px, -24px) rotate(180deg);
}

#carousel-wrapper #image {
    height: 240px;
    width: 240px;

    border-radius: 5px;

    background-position: center;
    background-repeat: no-repeat;
    background-size: cover;
}

#carousel-wrapper.anim-previous,
#carousel-wrapper.anim-next {
    pointer-events: none;
}
#carousel-wrapper.anim-next #current-option-text1,
#carousel-wrapper.anim-next #current-option-text2 {
    animation: next-text 0.65s 0.085s;
}
#carousel-wrapper.anim-previous #image,
#carousel-wrapper.anim-next #image {
    animation: previous-next-image 0.65s 0.085s;
}

#carousel-wrapper.anim-previous #current-option-text1,
#carousel-wrapper.anim-previous #current-option-text2 {
    animation: previous-text 0.65s 0.085s;
}

@keyframes previous-text {
    50%, 55% {
        transform: translate(0%, 390px);
    }
    to {
        transform: translate(0%, 380px);
    }
}
@keyframes previous-next-image {
    0% {
        transform: scale(1);
        opacity: 1;
    }
    70% {
        transform: scale(1.1);
        opacity: 0;
    }
    100% {
        transform: scale(1);
        opacity: 1;
    }
}
@keyframes next-text {
    50%, 55% {
        transform: translate(0%, -390px);
    }
    to {
        transform: translate(0%, -380px);
    }
}

@media only screen and (max-width: 480px) {
    #carousel-wrapper #menu {
        height: 430px;
    }
    #carousel-wrapper, #carousel-wrapper #menu {
        flex-direction: column;
    }
    #carousel-wrapper #menu #current-option {
        width: 240px;
    }
}

That’s all there is. Here’s a screenshot.

Related posts