Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 32 additions & 44 deletions website/static/website/css/carousel_fade.css
Original file line number Diff line number Diff line change
@@ -1,53 +1,41 @@
/*
Hack to make the carousel transition using fade instead of slide
inspired from http://codepen.io/Rowno/pen/Afykb
*/

.carousel-fade .carousel-inner .item {
opacity: 0;
transition-property: opacity;
/* transition-duration: 2s; */
}

.carousel-fade .carousel-inner .active {
opacity: 1;
}

.carousel-fade .carousel-inner .active.left,.carousel-fade .carousel-inner .active.right {
* Crossfade carousel transition.
*
* Slides are stacked and crossfaded purely with opacity, driven by the
* `.active` class that carousel.js toggles. The carousel has a fixed height
* (see `.carousel` / `.shortCarousel` in base.css), so absolutely positioning
* the slides does not collapse the container.
*
* Previously this file relied on Bootstrap 3's slide-transition classes
* (.next/.prev/.left/.right); that machinery went away with the Bootstrap
* carousel JS (Track A — issues #1288 / #1253), so the fade is now self-
* contained CSS rather than a hack layered on Bootstrap's animation.
*/

.carousel-fade .carousel-inner > .item {
position: absolute;
top: 0;
left: 0;
width: 100%;
/* Override Bootstrap's display:none on inactive items so opacity can animate. */
display: block;
opacity: 0;
z-index: 1;
z-index: 0;
/* Only the visible slide should capture clicks. */
pointer-events: none;
transition: opacity 0.6s ease-in-out;
}

.carousel-fade .carousel-inner .next.left,.carousel-fade .carousel-inner .prev.right {
.carousel-fade .carousel-inner > .item.active {
opacity: 1;
z-index: 1;
pointer-events: auto;
}

.carousel-fade .carousel-control {
z-index: 2;
}

/*
WHAT IS NEW IN 3.3: "Added transforms to improve carousel performance in modern browsers."
now override the 3.3 new styles for modern browsers & apply opacity
*/

@media all and (transform-3d), (-webkit-transform-3d) {
.carousel-fade .carousel-inner > .item.next, .carousel-fade .carousel-inner > .item.active.right {
opacity: 0;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
/* Respect users who prefer reduced motion: switch instantly, no crossfade.
(carousel.js also disables autoplay under this preference.) */
@media (prefers-reduced-motion: reduce) {
.carousel-fade .carousel-inner > .item {
transition: none;
}

.carousel-fade .carousel-inner > .item.prev, .carousel-fade .carousel-inner > .item.active.left {
opacity: 0;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}

.carousel-fade .carousel-inner > .item.next.left, .carousel-fade .carousel-inner > .item.prev.right, .carousel-fade .carousel-inner > .item.active {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}
}
182 changes: 182 additions & 0 deletions website/static/website/js/carousel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*!
* Carousel — vanilla JS, no jQuery / Bootstrap JS.
*
* Auto-rotating crossfade banner with clickable indicator dots, replacing
* Bootstrap 3's carousel plugin (Track A, see issues #1288 / #1253). It works
* with the existing markup: a `.carousel` containing `.carousel-inner > .item`
* slides (one marked `.active`) and an optional `.carousel-indicators > li`
* dot list. The crossfade itself is pure CSS (carousel_fade.css); this script
* only moves the `.active` class and runs the autoplay timer.
*
* Per-carousel options (data attributes on the `.carousel` element):
* - data-interval: autoplay delay in ms (default 5000; <= 0 disables autoplay).
* - data-pause="false": do NOT pause on mouse hover (default is to pause).
*
* Accessibility:
* - Honors prefers-reduced-motion: no autoplay and (via CSS) no crossfade.
* - Pauses while focus is inside the carousel (keyboard users reading links)
* and while the browser tab is hidden.
* - Marks the active indicator dot with aria-current.
*
* Note: there are no prev/next arrows or swipe gestures — this matches the
* previous Bootstrap setup, which had neither.
*/
(function () {
'use strict';

var prefersReducedMotion = window.matchMedia
? window.matchMedia('(prefers-reduced-motion: reduce)').matches
: false;

function setupCarousel(root) {
var inner = root.querySelector('.carousel-inner');
if (!inner) {
return;
}

var slides = Array.prototype.slice.call(inner.querySelectorAll(':scope > .item'));
if (slides.length === 0) {
return;
}

var indicators = Array.prototype.slice.call(
root.querySelectorAll('.carousel-indicators > li')
);

// Start from whichever slide the template marked active (default first).
var current = slides.findIndex(function (slide) {
return slide.classList.contains('active');
});
if (current < 0) {
current = 0;
}

/**
* Shows the slide at `index`, updates the indicator dots, and plays only
* the active slide's video (if any) to avoid decoding hidden videos.
*/
function render(index) {
slides.forEach(function (slide, i) {
var isActive = i === index;
slide.classList.toggle('active', isActive);

var video = slide.querySelector('video');
if (video) {
if (isActive) {
var playback = video.play();
if (playback && playback.catch) {
playback.catch(function () { /* autoplay blocked — ignore */ });
}
} else {
video.pause();
}
}
});

indicators.forEach(function (dot, i) {
var isActive = i === index;
dot.classList.toggle('active', isActive);
if (isActive) {
dot.setAttribute('aria-current', 'true');
} else {
dot.removeAttribute('aria-current');
}
});

current = index;
}

render(current);

// A single slide has nothing to rotate or navigate.
if (slides.length < 2) {
return;
}

/* ----------------------------- Autoplay ----------------------------- */

var intervalAttr = root.getAttribute('data-interval');
var interval = intervalAttr == null ? 5000 : parseInt(intervalAttr, 10);
var pauseOnHover = root.getAttribute('data-pause') !== 'false';

var timer = null;
var paused = false;

function showNext() {
render((current + 1) % slides.length);
}

function canPlay() {
return !prefersReducedMotion && interval > 0 && !paused && !document.hidden;
}

function start() {
stop();
if (canPlay()) {
timer = window.setInterval(showNext, interval);
}
}

function stop() {
if (timer !== null) {
window.clearInterval(timer);
timer = null;
}
}

function restart() {
// After a manual jump, give the next auto-advance a full interval.
stop();
start();
}

/* ---------------------------- Indicators ---------------------------- */

indicators.forEach(function (dot, i) {
dot.addEventListener('click', function () {
render(i);
restart();
});
});

/* -------------------------- Pause conditions ------------------------ */

if (pauseOnHover) {
root.addEventListener('mouseenter', function () {
paused = true;
stop();
});
root.addEventListener('mouseleave', function () {
paused = false;
start();
});
}

// Pause while focus is inside the carousel (keyboard users on slide links).
root.addEventListener('focusin', function () {
paused = true;
stop();
});
root.addEventListener('focusout', function (event) {
if (!root.contains(event.relatedTarget)) {
paused = false;
start();
}
});

// Pause when the tab is hidden; resume when it becomes visible again.
document.addEventListener('visibilitychange', function () {
if (document.hidden) {
stop();
} else {
start();
}
});

start();
}

document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.carousel').forEach(setupCarousel);
});
})();
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
{% load cropping %}
{% load ml_tags %}

<div id="main-carousel" class="carousel slide carousel-fade shortCarousel" data-ride="carousel" data-interval="10000"
<div id="main-carousel" class="carousel slide carousel-fade shortCarousel" data-interval="10000"
data-pause="false">
<div class="page-title shadow-left">
<div class="container carousel-container">
Expand Down Expand Up @@ -45,8 +45,7 @@
</div>
<ol class="carousel-indicators" {% if banners|length < 2 %} style="display: none" {% endif %}>
{% for banner in banners %}
<li data-target="#mainCarousel" data-slide-to="{{ forloop.counter0 }}"
{% if forloop.first %}class="active"{% endif %}></li>
<li {% if forloop.first %}class="active"{% endif %}></li>
{% endfor %}
</ol>
</div>
14 changes: 4 additions & 10 deletions website/templates/website/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,14 @@
crossorigin="anonymous"></script>
<script src="{% static 'website/js/jquery.easing.min.js' %}"></script>
<script src="{% static 'website/js/top-navbar.js' %}"></script>
<script src="{% static 'website/js/carousel.js' %}"></script>

{% block external_scripts %}{% endblock %}

<title>{% block pagetitle %}{{ request.resolver_match.url_name }}{% endblock %} | Makeability Lab</title>

<script>
{% block scripts %}{% endblock %}

$(document).ready(function() {
$('.carousel').carousel();
});
</script>

{% if debug %}
Expand Down Expand Up @@ -261,9 +258,8 @@
- Nested links are allowed within region (no nested-interactive violation)
{% endcomment %}
{% block maincarousel %}
<div id="main-carousel"
class="carousel slide carousel-fade"
data-ride="carousel"
<div id="main-carousel"
class="carousel slide carousel-fade"
data-interval="10000"
data-pause="true"
role="region"
Expand Down Expand Up @@ -321,9 +317,7 @@
{% if banners|length < 2 %}style="display: none"{% endif %}
aria-label="Carousel navigation">
{% for banner in banners %}
<li data-target="#main-carousel"
data-slide-to="{{ forloop.counter0 }}"
{% if forloop.first %}class="active"{% endif %}
<li {% if forloop.first %}class="active"{% endif %}
aria-label="Slide {{ forloop.counter }}">
</li>
{% endfor %}
Expand Down
Loading