I have created a demo below for the bootstrap 4 carousel having transitions slowed down to 30ses, so that everyone can inspect and have a proper look at the dynamic css being added by js:
.carousel-item {
transition: transform 30s ease-in-out!important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://getbootstrap.com/docs/4.6/dist/js/bootstrap.bundle.js"></script>
<link href="https://getbootstrap.com/docs/4.6/dist/css/bootstrap.css" rel="stylesheet"/>
<div id="carouselExampleCaptions" class="carousel slide" data-ride="carousel">
<ol class="carousel-indicators">
<li data-target="#carouselExampleCaptions" data-slide-to="0" class="active"></li>
<li data-target="#carouselExampleCaptions" data-slide-to="1"></li>
<li data-target="#carouselExampleCaptions" data-slide-to="2"></li>
</ol>
<div class="carousel-inner">
<div class="carousel-item active">
<svg class="bd-placeholder-img bd-placeholder-img-lg d-block w-100" width="800" height="400" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: First slide" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#777"></rect><text x="50%" y="50%" fill="#555" dy=".3em">First slide</text></svg>
</div>
<div class="carousel-item">
<svg class="bd-placeholder-img bd-placeholder-img-lg d-block w-100" width="800" height="400" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: First slide" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#777"></rect><text x="50%" y="50%" fill="#555" dy=".3em">Second slide</text></svg>
</div>
<div class="carousel-item">
<svg class="bd-placeholder-img bd-placeholder-img-lg d-block w-100" width="800" height="400" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: First slide" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#777"></rect><text x="50%" y="50%" fill="#555" dy=".3em">Third slide</text></svg>
</div>
</div>
<a class="carousel-control-prev" href="#carouselExampleCaptions" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carouselExampleCaptions" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
As you can see the all slides are given margin-right: -100%
and the active slide is then transformed translateX(-100%)
. What I don't get is:
Question 1: The margin-right -100%
should create negative space for each slide, making all of them appearing on top of each other, but in reality they come side to side, why?
Question 2: If only the active slide is being transformed, why does the slide on right also get transformed with the active slide?
For question 1 consider the following demo: https://jsfiddle.net/2j1ug8x4/ here you can see how 1
and 2
come at the same place. They are given float: left
as well just like bootstrap.
Question 1:
The margin-right
actually is placing the slides on top of one another - it's the CSS transform that fixes this and makes the slides appear next to each other. Take a look at this example where the slides are forced to display: block
and you'll see the third slide, with the other two stacked beneath.
The real reason for using margin-right: -100%
is to make sure the slides appear in a row, rather than stacking vertically. See this example where the slides are visible and the negative margins removed - you can scroll down to see all the slides are stacked vertically.
Question 2:
So why does the slide on the right also get transformed? This one is a little tricky to debug, but what I think is happening is bootstrap is adding two classes to the right slide: carousel-item-next
and carousel-item-left
If you look at the source CSS, carousel-item-next
adds a transform: translateX(100%)
to the element. But that CSS rule only includes the transform if the second class is not present:
.carousel-item-next:not(.carousel-item-left),
transform: translateX(100%);
}
So what I believe is happening is that bootstrap is adding the first class (carousel-item-next
), then there is a slight delay before the second class (carousel-item-left
) is added.
That delay means the right slide is briefly given a transform of translateX(100%)
which is then immediately removed. Because there is a transition on the slide elements, this causes the right slide to animate from translateX(100%)
to translateX(0)
, or from right to left.
Here is a simplified example replicating this behaviour without bootstrap - click on the Move
button to trigger the movement. Note the setTimeout
used to artificially simulating a gap between the adding of the first and second CSS classes:
const button = document.getElementById('move');
button.addEventListener('click', () => {
const childElements = document.querySelectorAll('.child');
childElements[0].classList.toggle('left');
childElements[1].classList.toggle('right');
setTimeout(() => {
childElements[1].classList.toggle('skip');
}, 0);
});
#move {
position: absolute;
top: 10px;
left: 55%;
}
.container {
position: relative;
width: 50%;
}
.child {
display: none;
position: relative;
width: 100%;
height: 300px;
border: 1px dotted blue;
float: left;
margin-right: -100%;
transition: 1s transform ease;
}
.left {
transform: translateX(-100%);
}
.right:not(.skip) {
transform: translateX(100%);
}
.active,
.right {
display: block;
}
<div class="container">
<div class="child active">1</div>
<div class="child">2</div>
</div>
<button id="move">Move</button>