问题
I have created an image slider with many images using some javascript and css. I just used client width to get the size of the image (which vary slightly) and calculated the translateX distance with a counter variable. Added a css transition in the end. However I can't seem to get the slider to translate the whole image correctly. I don't know why it's going wrong. I have used 'vw' in the calculations for responsiveness. I am new to javascript and would love any tips for other parts for other parts of code as well.
here is the JS fiddle link- https://jsfiddle.net/n6smpv2j/15/
HTML
<div id="lookbook" data-tab-content class="black-text">
<div class="lookbook-nav">
<button id="left">←</button>
<button id="right">→</button>
</div>
<div class="lookbook">
<div class="slider">
<img src="https://loremflickr.com/640/360" id="lastClone" alt="">
<img src="https://picsum.photos/640/400">
<img src="https://loremflickr.com/640/360">
<img src="https://picsum.photos/640/400">
<img src="https://loremflickr.com/640/360">
<img src="https://picsum.photos/640/400">
<img src="https://loremflickr.com/640/360">
<img src="https://picsum.photos/600/400">
<img src="https://fillmurray.com/600/330">
<img src="https://picsum.photos/600/400">
<img src="https://fillmurray.com/600/330">
<img src="https://picsum.photos/600/400">
<img src="https://loremflickr.com/640/360">
<img src="https://picsum.photos/600/400">
<img src="https://loremflickr.com/640/360">
<img src="https://picsum.photos/600/400" id="firstClone" alt="">
</div>
</div>
</div>
JS
const slider = document.querySelector('.slider');
const sliderImages = document.querySelectorAll('.slider img');
const leftbtn = document.querySelector('#left');
const rightbtn = document.querySelector('#right');
let counter = 1;
const size = sliderImages[0].clientWidth;
slider.style.transform = 'translateX(' + (-size * counter) + 'vw)';
rightbtn.addEventListener('click', () => {
if (counter >= sliderImages.length - 1) return;
slider.style.transition = "transform 0.4s ease-in";
counter++;
slider.style.transform = 'translateX(' + (-size * counter) + 'vw)'
})
leftbtn.addEventListener('click', () => {
if (counter <= 0) return;
slider.style.transition = "transform 0.4s ease-in";
counter--;
slider.style.transform = 'translateX(' + (-size * counter) + 'vw)'
})
slider.addEventListener('transitionend', () => {
if (sliderImages[counter].id === "lastClone") {
slider.style.transition = "none";
counter = sliderImages.length - 2;
slider.style.transform = 'translateX(' + (-size * counter) + 'vw)'
}
if (sliderImages[counter].id === "firstClone") {
slider.style.transition = "none";
counter = sliderImages.length - counter;
slider.style.transform = 'translateX(' + (-size * counter) + 'vw)'
}
})
CSS
#lookbook {
width: 100vw;
height: 100vh;
}
.lookbook-nav {
width: 70vw;
height: 10vh;
margin-left: 15vw;
margin-top: 45vh;
position: absolute;
display: flex;
justify-content: space-between;
align-items: center;
}
button {
border: none;
outline: none;
background: transparent;
font-size: 2rem;
/* font-weight: bold; */
cursor: pointer;
}
.lookbook-nav button {
border: none;
outline: none;
background: transparent;
font-size: 2rem;
/* font-weight: bold; */
cursor: pointer;
}
button:hover {
opacity: 0.4;
}
.lookbook {
width: 56vw;
height: 91vh;
margin: auto;
overflow: hidden;
}
.lookbook img {
width: 100%;
height: auto !important;
}
.slider {
margin-top: 10vh;
display: flex;
width: auto;
}
回答1:
The answer from @DecjazMach solves the most important problem but doesn't cover everything. For example, the solution also still uses the width of the first image to set the width of the visible slider. This will be fine in many cases, but what if the first image is a skinny tall portrait and the rest landscape or vice versa?
@Laiqa Mohid also welcomed any other suggestions so here are some which come out of trying to simplify things, for example minimising the calculation needed in the JS and the 'work' the system has to do on a click. You can try it here http://bayeuxtapestry.rgspaces.org.uk/slider
Notes:
The size of the visible portion of the slider is not dependent on the dimensions of the first image
imgs have been replaced with divs + background-image so that different sizes/aspect ratios can be accommodated without any need for javascript calculation - this automatically helps responsiveness
these divs are all of the same dimensions so the amount the slider needs to move does not depend on the size of the image
images that do not fill the whole width (because they are too tall relatively) will be centred
images are also centred vertically. This can be changed if required (e.g. to align to the top of the slider) by changing the background-position in .slider div
Using a transform:translateX works but requires a calculation in the Javascript. We can use CSS animation instead and need only move the currently visible slide and the one that is to be shown next.
The image serving services sometimes did not serve an image so I have used my own - deliberately of different sizes and aspect ratios (including portrait)
Using this method it is possible to have a continuous slider - showing the first slide if the user clicks past the last one.
Here is the code:
<!DOCTYPE html>
<html>
<head>
<title>Slider</title>
<meta charset="utf-8">
<style>
#lookbook {
width: 100vw;
height: 100vh;
margin:0;
padding:0;
overflow:hidden;
}
.lookbook-nav {
width: 70vw;
height: 10vh;
margin-left: 15vw;
margin-top: 45vh;
position: absolute;
display: flex;
justify-content: space-between;
align-items: center;
}
button {
border: none;
outline: none;
background: transparent;
font-size: 2rem;
/* font-weight: bold; */
cursor: pointer;
}
.lookbook-nav button {
border: none;
outline: none;
background: transparent;
font-size: 2rem;
/* font-weight: bold; */
cursor: pointer;
}
button:hover {
opacity: 0.4;
}
div .lookbook {
width: 56vw;
}
.lookbook {
height: 91vh;
margin: auto;
overflow: hidden;
}
div.slider{
margin:0;
margin-top: 10vh;
height:81vh;/* this is height of (lookbook - margin-top) - probably better done through flex */
position:relative;
top:0;
padding:0;
width:100%;
}
@keyframes slideouttoleft {
from {
left: 0;
visibility:visible;
}
to {
left: -100%;
visibility:hidden;
}
}
@keyframes slideinfromright {
from {
left: 100%;
visibility:visible;
}
to {
left: 0;
visibility:visible;
}
}
@keyframes slideouttoright {
from {
left: 0;
visibility:visible;
}
to {
left: 100%;
visibility:hidden;
}
}
@keyframes slideinfromleft {
from {
left: -100%;
visibility:visible;
}
to {
left: 0;
visibility:visible;
}
}
.slider div {
position:absolute;
top:0;
left:0;
overflow:hidden;
visibility:hidden;
margin: 0;
padding: 0;
width:100%;
height:100%;
background-size: contain;
background-position: center center;
background-repeat: no-repeat no-repeat;
animation-duration: 0.4s;
animation-delay: 0s;
animation-iteration-count: 1;
animation-direction: normal;
animation-timing-function: ease-in;
animation-fill-mode: forwards;
}
</style>
</head>
<body>
<div id="lookbook" data-tab-content class="black-text">
<div class="lookbook-nav">
<button id="left">←</button>
<button id="right">→</button>
</div>
<div class="lookbook">
<div class="slider">
<!-- images taken from Reading (UK) Museum's Victorian copy of the Bayeux Tapestry -->
<div style="background-image:url(https://rgspaces.org.uk/bayeuxtapestry/wp-content/uploads/boat-and-horses-768x546.png);"></div>
<div style="background-image:url(https://rgspaces.org.uk/bayeuxtapestry/wp-content/uploads/two-horses-300x212.png);"></div>
<div style="background-image:url(https://rgspaces.org.uk/bayeuxtapestry/wp-content/uploads/woman-and-child-1200x901.png);"></div>
<div style="background-image:url(https://rgspaces.org.uk/bayeuxtapestry/wp-content/uploads/archer-2-768x1100.png);"></div>
<div style="background-image:url(https://rgspaces.org.uk/bayeuxtapestry/wp-content/uploads/boat-builder-2-878x1024.png);"></div>
<div style="background-image:url(https://rgspaces.org.uk/bayeuxtapestry/wp-content/uploads/group-1-768x603.png);"></div>
<div style="background-image:url(https://rgspaces.org.uk/bayeuxtapestry/wp-content/uploads/pointing-horseman-768x853.png);"></div>
<div style="background-image:url(https://rgspaces.org.uk/bayeuxtapestry/wp-content/uploads/group-2-768x619.png);"></div>
<div style="background-image:url(https://rgspaces.org.uk/bayeuxtapestry/wp-content/uploads/carrying-casket-768x556.png);"></div>
</div>
</div>
</div>
<script>
const slider = document.querySelector('.slider');
const sliderImages = document.querySelectorAll('.slider div');
const leftbtn = document.querySelector('#left');
const rightbtn = document.querySelector('#right');
const numImgs=sliderImages.length;
let curImg = 0;
rightbtn.addEventListener('click', () => {
sliderImages[curImg].style.animationName='slideouttoleft';
curImg=(curImg+1)%numImgs;
sliderImages[curImg].style.animationName='slideinfromright';
})
leftbtn.addEventListener('click', () => {
sliderImages[curImg].style.animationName='slideouttoright';
curImg=curImg==0? numImgs-1 : Math.abs((curImg-1)%numImgs);
sliderImages[curImg].style.animationName='slideinfromleft';
})
function initialize() {
sliderImages[0].style.animationName='slideinfromright';
}
window.onload=initialize;
</script>
</body>
</html>
回答2:
That is because the size
is being calculated in pixels as you can see here. So to get the width in vw
you can use the following function as
const size = vw(sliderImages[0].clientWidth);
function vw(v) {
var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
return (v * w) / 100;
}
回答3:
For some reason, the images loaded from that source didn't work so I downloaded them locally and they did work and I've done some modification to your CSS as well.
var slider = document.getElementById("slider");
var slides = slider.childElementCount;
var i = 0;
document.getElementById("right").addEventListener("click", function () {
i == slides - 1 ? (i = 0) : i++;
slider.style.transform = "translate(-" + 600 * i + "px)";
});
body {
background-color: aqua;
}
#lookbook {
position: relative;
box-sizing: content-box;
height: auto;
max-width: 600px;
margin: auto;
}
.lookbook-nav {
position: absolute;
display: flex;
justify-content: space-between;
width: 100%;
height: 100%;
}
button {
border: none;
outline: none;
background: transparent;
font-size: 2rem;
cursor: pointer;
}
.lookbook-nav button {
border: none;
outline: none;
background: transparent;
font-size: 2rem;
/* font-weight: bold; */
cursor: pointer;
color: beige;
z-index: 2;
}
button:hover {
opacity: 0.4;
}
.lookbook {
width: auto;
height: 91vh;
margin: auto;
overflow: hidden;
}
.lookbook img {
width: 600px;
height: auto !important;
}
.slider {
margin-top: 10vh;
display: flex;
/* align-items: flex-end; */
width: auto;
/* height: 700px; */
transition: 0.5s ease-in-out;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Slider</title>
</head>
<body>
<div id="lookbook" data-tab-content class="black-text">
<div class="lookbook-nav">
<button id="left">←</button>
<button id="right">→</button>
</div>
<div class="lookbook">
<div class="slider" id="slider">
<img src="https://picsum.photos/600/360" alt="" />
<img src="https://picsum.photos/600/360" alt="" />
<img src="https://picsum.photos/600/360" alt="" />
<img src="https://picsum.photos/600/360" alt="" />
<img src="https://picsum.photos/600/360" alt="" />
<img src="https://picsum.photos/600/360" alt="" />
</div>
</div>
</div>
</body>
</html>
I just made one navigation arrow work but should be the same thing just in reverse order also you don't have to worry about the counter as it will detect how many images you have inside the slider.
来源:https://stackoverflow.com/questions/63861444/vanilla-javascript-css-image-slider-not-working-properly