Hi,
i’m trying to implement Slider/Carousel Jekyll Codex in Jekyll-Garden theme, but my main issue is that the carousel images are all cropped to fit 1:1 aspect ratio.
I also tried (in the second version shared) to change the effect from slide to fade and make the image zoomable (without autosliding, like ), but i failed (The image get stuck zoomed in and can’t exit the zoom without reloading the page).
What I’m trying to achieve is something similar to the “Slider using Controls + Filters + Lightbox” in the page Image Content - Lightboxes, Carousels, Sliders, and Galleries for J1 Template (the one in “Featured MS Slider”)
Here is my modified code of Carousel.html
:
{% assign letterstring = "a,b,c,d,e,f,g,h,i,j,k,l,m,n" %}
{% assign letters = letterstring | split: ',' %}
{% assign number = include.number | minus: 1 %}
<div class="carousel__holder">
<div id="carousel{{ number }}" class="carousel">
{% for item in page.carousels[number].images %}
<input class="carousel__activator" type="radio" name="carousel{{ number }}" id="{{ number }}{{ letters[forloop.index0] }}" {% if forloop.first %}checked="checked"{% endif %} />
{% endfor %}
{% for item in page.carousels[number].images %}
{% if forloop.index == forloop.length %}
{% assign nextindex = 0 %}
{% else %}
{% assign nextindex = forloop.index0 | plus: 1 %}
{% endif %}
{% assign nextletter = letters[nextindex] %}
{% if forloop.index0 == 0 %}
{% assign previndex = forloop.length | minus: 1 %}
{% else %}
{% assign previndex = forloop.index0 | minus: 1 %}
{% endif %}
{% assign prevletter = letters[previndex] %}
<div class="carousel__controls">
<label class="carousel__control carousel__control--backward" for="{{ number }}{{ prevletter }}"></label>
<label class="carousel__control carousel__control--forward" for="{{ number }}{{ nextletter }}"></label>
</div>
{% endfor %}
<div class="carousel__track">
<ul>
{% for item in page.carousels[number].images %}
<li class="carousel__slide" style="background-image: url('{{ item.image }}');"></li>
{% endfor %}
</ul>
</div>
<div class="carousel__indicators">
{% for item in page.carousels[number].images %}
<label class="carousel__indicator" for="{{ number }}{{ letters[forloop.index0] }}"></label>
{% endfor %}
</div>
</div>
</div>
<style>
.carousel__holder {width: 100%; position: relative; padding-bottom: {{ include.height }}{{ include.unit }}; margin: 1rem 0 1rem;}
.carousel {
height: 100%;
width: 100%;
overflow: hidden;
text-align: center;
position: absolute;
padding: 0;
}
.carousel__controls,
.carousel__activator {
display: none;
}
{% for item in page.carousels[number].images %}
.carousel__activator:nth-of-type({{ forloop.index }}):checked ~ .carousel__track {
-webkit-transform: translateX(-{{ forloop.index0 }}00%);
transform: translateX(-{{ forloop.index0 }}00%);
}
.carousel__activator:nth-of-type({{ forloop.index }}):checked ~ .carousel__slide:nth-of-type({{ forloop.index }}) {
transition: opacity 0.5s, -webkit-transform 0.5s;
transition: opacity 0.5s, transform 0.5s;
transition: opacity 0.5s, transform 0.5s, -webkit-transform 0.5s;
top: 0;
left: 0;
right: 0;
opacity: 1;
-webkit-transform: scale(1);
transform: scale(1);
}
.carousel__activator:nth-of-type({{ forloop.index }}):checked ~ .carousel__controls:nth-of-type({{ forloop.index }}) {
display: block;
opacity: 1;
}
.carousel__activator:nth-of-type({{ forloop.index }}):checked ~ .carousel__indicators .carousel__indicator:nth-of-type({{ forloop.index }}) {
opacity: 1;
}
{% endfor %}
.carousel__control {
height: 30px;
width: 30px;
margin-top: -15px;
top: 50%;
position: absolute;
display: block;
cursor: pointer;
border-width: 5px 5px 0 0;
border-style: solid;
border-color: #fafafa;
opacity: 0.35;
opacity: 1;
outline: 0;
z-index: 3;
}
.carousel__control:hover {
opacity: 1;
}
.carousel__control--backward {
left: 20px;
-webkit-transform: rotate(-135deg);
transform: rotate(-135deg);
}
.carousel__control--forward {
right: 20px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
.carousel__indicators {
position: absolute;
bottom: 2%;
width: 100%;
text-align: center;
}
.carousel__indicator {
height: 15px;
width: 15px;
border-radius: 100%;
display: inline-block;
z-index: 2;
cursor: pointer;
opacity: 0.35;
margin: 0 2.5px 0 2.5px;
}
.carousel__indicator:hover {
opacity: 0.75;
}
.carousel__track {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: 0;
margin: 0;
transition: -webkit-transform 0.5s ease 0s;
transition: transform 0.5s ease 0s;
transition: transform 0.5s ease 0s, -webkit-transform 0.5s ease 0s;
}
.carousel__track .carousel__slide {
display: block;
top: 0;
left: 0;
right: 0;
opacity: 1;
}
{% for item in page.carousels[number].images %}
.carousel__track .carousel__slide:nth-of-type({{ forloop.index }}) {
-webkit-transform: translateX({{ forloop.index0 }}00%);
transform: translateX({{ forloop.index0 }}00%);
}
{% endfor %}
.carousel--scale .carousel__slide {
-webkit-transform: scale(0);
transform: scale(0);
}
.carousel__slide {
height: 100%;
position: absolute;
opacity: 0;
overflow: hidden;
}
.carousel__slide .overlay {height: 100%;}
.carousel--thumb .carousel__indicator {
height: 30px;
width: 30px;
}
.carousel__indicator {
background-color: #fafafa;
}
{% for item in page.carousels[number].images %}
.carousel__slide:nth-of-type({{ forloop.index }}),
.carousel--thumb .carousel__indicators .carousel__indicator:nth-of-type({{ forloop.index }}) {
background-size: cover;
background-position: center;
}
{% endfor %}
</style>
<script>
function isVisible(el) {
while (el) {
if (el === document) {
return true;
}
var $style = window.getComputedStyle(el, null);
if (!el) {
return false;
} else if (!$style) {
return false;
} else if ($style.display === 'none') {
return false;
} else if ($style.visibility === 'hidden') {
return false;
} else if (+$style.opacity === 0) {
return false;
} else if (($style.display === 'block' || $style.display === 'inline-block') &&
$style.height === '0px' && $style.overflow === 'hidden') {
return false;
} else {
return $style.position === 'fixed' || isVisible(el.parentNode);
}
}
}
{% if include.duration %}
setInterval(function(){
var j=0;
var elements = document.querySelectorAll('#carousel{{ number}} .carousel__control--forward');
for(i=(elements.length - 1);i>-1;i--) {
if(isVisible(elements[i])) j=i;
}
elements[j].click();
},{{ include.duration }}000);
{% endif %}
</script>
and here is my markdown post:
---
title: Test Carousel
feed: show
date: 2023-07-23 14:00
carousels:
- images:
- image: https://jekyllcodex.org/uploads/slider/samuel-zeller-356338_scaled.jpg
- image: https://jekyllcodex.org/uploads/slider/ricardo-gomez-angel-180819_scaled.jpg
---
{% include Carousel.html height="100" unit="%" duration="7" number="1" %}
This is the second version i was trying to make with zoomable images and fading effect (using bootstrap and nodejs) but it’s quite a bit broken (the boostrap.css
is a custom file i copied only some boostrap styles to not overwrite the theme main style, while nodejs and bootstrap script files were hosted locally but can linked to cdn urls as well):
{% assign number = include.number | minus: 1 | default: "0" %}
{% assign effect = include.effect | default: "slide" %}
{% assign duration = include.duration | default: "0" %}
{% assign ride = "false" %}
{% if duration == "0" %}
{% assign interval = "false" %}
{% else %}
{% assign ride = "carousel" %}
{% assign interval = duration | times: 1000 %}
{% endif %}
{% assign tag_n = number | plus: 1 %}
<link href="{{ site.baseurl }}/assets/css/bootstrap.css" rel="stylesheet" />
<div id="carousel_{{ number }}" class="carousel slide carousel-{{ effect }}" data-bs-ride="{{ ride }}">
<div class="carousel-inner">
{% for item in page.carousels[number].images %}
<div class="carousel-item{% if forloop.first %} active{% endif %}" data-bs-interval="{{ interval }}">
<img src="{{ item.image }}" class="zoomable-image d-block w-100" alt="{% if page.carousels[tag_n].tags[forloop.index0].tag %}{{ page.carousels[tag_n].tags[forloop.index0].tag }}{% else %}image_{{ forloop.index }}{% endif %}">
</div>
{% endfor %}
</div>
<div class="overlay"></div>
<button class="carousel-control-prev {% if page.carousels[number].images.size <= 1 %}hidden{% endif %}" type="button" data-bs-target="#carousel_{{ number }}" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next {% if page.carousels[number].images.size <= 1 %}hidden{% endif %}" type="button" data-bs-target="#carousel_{{ number }}" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</div>
<style>
.zoomable-image {
cursor: pointer;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 9998; /* Ensure the overlay is behind the fullscreen image */
pointer-events: none; /* Allow interaction with elements behind the overlay */
}
.zoomable-image.fullscreen {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
object-fit: contain;
z-index: 9999;
pointer-events: auto; /* Allow interaction with the fullscreen image */
transition: background 1s ease !important; /* Apply the transition to background */
background: rgba(0, 0, 0, 0.6); /* Apply opacity to background */
/*backdrop-filter: blur(0.03em);*/ /* Apply blur to background */
}
.hidden {
display: none; /* Hide the buttons */
}
</style>
<script src="{{ site.baseurl }}/assets/js/jquery-3.7.1.min.js"></script>
<script src="{{ site.baseurl }}/assets/js/bootstrap.bundle.min.js"></script>
<script>
document.querySelectorAll('.zoomable-image').forEach(image => {
image.addEventListener('click', function() {
const carousel = document.getElementById('carousel_{{ number }}');
if (!this.classList.contains('fullscreen')) {
this.style.transition = 'transform 0.5s ease-in-out'; // Apply the transition duration
scrollPosition = window.scrollY; // Store the current scroll position
setTimeout(() => {
this.classList.add('fullscreen');
$(carousel).carousel('pause');
document.querySelector('.overlay').classList.add('show'); // Toggle the overlay
document.querySelector('.carousel-control-prev').classList.add('hidden'); // Toggle the previous button
document.querySelector('.carousel-control-next').classList.add('hidden'); // Toggle the next button
document.querySelector('.navbar').style.position = 'static'; // Set the navbar position to static
document.body.style.overflow = 'hidden'; // Disable scrolling
document.body.style.position = 'fixed';
document.body.style.width = '100%';
document.addEventListener('keydown', exitFullscreenOnEsc); // Listen for the "Esc" key
}, 0); // Delay the class addition to allow the transition to take effect
} else {
this.style.transition = 'transform 0.5s ease-in-out'; // Apply the transition duration
this.classList.remove('fullscreen');
$(carousel).carousel();
document.querySelector('.overlay').classList.remove('show'); // Toggle the overlay
document.querySelector('.carousel-control-prev').classList.remove('hidden'); // Toggle the previous button
document.querySelector('.carousel-control-next').classList.remove('hidden'); // Toggle the next button
document.querySelector('.navbar').style.position = 'relative'; // Reset the navbar position
document.body.style.overflow = 'auto'; // Enable scrolling
document.body.style.position = '';
document.body.style.width = '';
document.removeEventListener('keydown', exitFullscreenOnEsc); // Remove the "Esc" key listener
window.scrollTo(0, scrollPosition); // Restore the scroll position
}
});
});
function exitFullscreenOnEsc(event) {
if (event.key === "Escape" || event.key === "Esc" || event.keyCode === 27) {
document.querySelectorAll('.zoomable-image.fullscreen').forEach(image => {
image.click(); // Exit fullscreen when "Esc" key is pressed
});
}
}
</script>
Here is the bootstrap.css
:
* {
box-sizing: border-box;
}
img {
vertical-align: middle;
}
button {
border-radius: 0px;
}
button {
margin: 0px;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button {
text-transform: none;
}
[type=button],
button {
appearance: button;
}
[type=button]:not(:disabled),
button:not(:disabled) {
cursor: pointer;
}
.container,
.container-fluid,
.container-xxl,
.container-xl,
.container-lg,
.container-md,
.container-sm {
width: 100%;
padding-right: var(--bs-gutter-x, 0.75rem);
padding-left: var(--bs-gutter-x, 0.75rem);
margin-right: auto;
margin-left: auto;
}
.carousel {
position: relative;
}
.carousel.pointer-event {
touch-action: pan-y;
}
.carousel-inner {
position: relative;
width: 100%;
overflow: hidden;
}
.carousel-inner::after {
display: block;
clear: both;
content: "";
}
.carousel-item {
position: relative;
display: none;
float: left;
width: 100%;
margin-right: -100%;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
transition: transform 0.6s ease-in-out;
}
@media (prefers-reduced-motion: reduce) {
.carousel-item {
transition: none;
}
}
.carousel-item.active,
.carousel-item-next,
.carousel-item-prev {
display: block;
}
/* rtl:begin:ignore */
.carousel-item-next:not(.carousel-item-start),
.active.carousel-item-end {
transform: translateX(100%);
}
.carousel-item-prev:not(.carousel-item-end),
.active.carousel-item-start {
transform: translateX(-100%);
}
/* rtl:end:ignore */
.carousel-fade .carousel-item {
opacity: 0;
transition-property: opacity;
transform: none;
}
.carousel-fade .carousel-item.active,
.carousel-fade .carousel-item-next.carousel-item-start,
.carousel-fade .carousel-item-prev.carousel-item-end {
z-index: 1;
opacity: 1;
}
.carousel-fade .active.carousel-item-start,
.carousel-fade .active.carousel-item-end {
z-index: 0;
opacity: 0;
transition: opacity 0s 0.6s;
}
@media (prefers-reduced-motion: reduce) {
.carousel-fade .active.carousel-item-start,
.carousel-fade .active.carousel-item-end {
transition: none;
}
}
.carousel-control-next,
.carousel-control-prev {
position: absolute;
top: 0px;
bottom: 0px;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
width: 15%;
padding: 0px;
color: rgb(255, 255, 255);
text-align: center;
background: 0px 0px;
border: 0px;
opacity: 0.5;
transition: opacity 0.15s ease 0s;
background-size: auto;
}
.carousel-control-next:hover,
.carousel-control-prev:hover {
color: rgb(255, 255, 255);
text-decoration: none;
outline: 0px;
opacity: 0.9;
}
.carousel-control-prev {
left: 0px;
}
.carousel-control-next {
right: 0px;
}
.carousel-control-next-icon,
.carousel-control-prev-icon {
display: inline-block;
width: 2rem;
height: 2rem;
background-repeat: no-repeat;
background-position: 50% center;
background-size: 100% 100%;
background-size: 100% 100%;
}
.carousel-control-prev-icon {
background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e");
}
.carousel-control-next-icon {
background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
}
.visually-hidden {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0px !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0px, 0px, 0px, 0px) !important;
white-space: nowrap !important;
border: 0px !important;
}
.d-block {
display: block !important;
}
.w-100 {
width: 100% !important;
}