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.
Note that the code below is the WordPress version, including the PHP code and the WP_Query
loops. For a clean, HTML version only, check the CodePen demo above.
<?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.
See the Pen JavaScript Boxed Carousel by Ciprian (@ciprian) on CodePen.