Trying to make continuous JavaScript slider - javascript

The problem with my slider is that when it gets to the last slide and i click next it jumps over the two slides to get to the first one. Similarly when i am on the first slide and click previous, it jumps over slides to get to the last one. I would like to make it that when i get to the last slide and click NEXT the first slide would come from the right to left. (similar concept for the PREVIOUS button on first slide). I tried using insertBefore() and appendChild() for the slides but couldn't figure it out...
Here is my code:
// Slider
const slider_wrapp = document.querySelector('.tract-slider');
const slider = document.querySelector('.tract-slider-wrapp');
var slide = document.getElementsByClassName('tract-slide');
const leftBtn = document.querySelector('.slide-left');
const rightBtn = document.querySelector('.slide-right');
let swWidth = slider_wrapp.clientWidth;
let sliderWidth = swWidth * slide.length;
let slideWidth = 0;
slider.style.width = sliderWidth + "px";
for (var i = 0; i < slide.length; i++) {
slide.item(i).style.width = swWidth + "px";
}
function moveRight() {
slideWidth === sliderWidth - swWidth ? slideWidth = 0 : slideWidth += swWidth;
slider.style.transform = "translateX(" + (-slideWidth) + "px)";
}
function moveLeft() {
slideWidth === 0 ? slideWidth = sliderWidth - swWidth : slideWidth -= swWidth;
slider.style.transform = "translateX(" + (-slideWidth) + "px)";
}
rightBtn.addEventListener("click", function() {
moveRight();
});
leftBtn.addEventListener("click", function() {
moveLeft();
});
.tract-slider {
width: 100%;
height: 75vh;
position: relative;
overflow: hidden;
}
.tract-slider-wrapp {
height: 100%;
position: relative;
-webkit-transition: all 350ms cubic-bezier(.08, .13, 0, .81);
-o-transition: all 350ms cubic-bezier(.08, .13, 0, .81);
transition: all 350ms cubic-bezier(.08, .13, 0, .81);
}
.tract-slide {
height: 100%;
float: left;
position: relative;
display: block;
background-position: center;
-webkit-background-size: cover;
background-size: cover;
}
.tract-slide:nth-child(1) {
background-image: url("https://static.pexels.com/photos/126282/pexels-photo-126282.jpeg");
}
.tract-slide:nth-child(2) {
background-image: url("https://static.pexels.com/photos/29017/pexels-photo-29017.jpg");
}
.tract-slide:nth-child(3) {
background-image: url("https://static.pexels.com/photos/70760/dandelion-dandelion-seeds-taraxacum-fluffy-70760.jpeg");
}
.tract-slider-control {
position: absolute;
left: 0;
bottom: 0;
background: #ffffff;
padding: 1em;
}
.tract-slider-btn {
display: inline-block;
cursor: pointer;
margin-left: 1em;
}
.tract-slider-btn:nth-child(1) {
margin-left: 0;
}
<div class="tract-slider">
<div class="tract-slider-wrapp">
<div class="tract-slide"></div>
<div class="tract-slide"></div>
<div class="tract-slide"></div>
</div>
<div class="tract-slider-control">
<div class="tract-slider-btn slide-left">Prev</div>
<div class="tract-slider-btn slide-right">Next</div>
</div>
</div>
PS. Please use JavaScript for solution

Creating an infinite slider means you need to move your slides around in DOM so they give the impression of a continuous track.
The first thing you need to change is having their backgrounds tied up to their position in DOM. If we want to slide back from first slide to the last one, we need to take the last slide, prepend it before the first one but, considering your current CSS, that will change the backgrounds of all slides, as they are currently bound to their position in DOM (...:nth-child {background-image:...}...).
The second thing that needs changing is positioning the slides into the slider track. If they're floated, whenever we change their order, all the rest of the slides will be affected. By positioning them with position:absolute each slide moves independently, without affecting the others, so it's easier to rearrange them while keeping control.
Long story short, I started from scratch and placed all methods inside a single object: theSlider.
The reset() function does the heavy lifting: it puts before class on first element, current on second and after on all the rest. So you have to put the "last" slide first, because the slider will start with it appended before the "current" one.
The sliding is done by applying go-left and go-right classes to the track. After the transition is done, I just move the first/last slide into the new position, depending on case, and run reset() again (which strips all classes and reapplies them based on new positions).
Animations are handled by CSS. All JavaScript does is apply/remove classes and move the slides in DOM.
var theSlider = {
track : document.querySelector('.tract-slider-wrapp'),
// has to match `transition-duration` in CSS:
duration : 600,
reset : function() {
var slides = document.querySelectorAll('.tract-slider-wrapp > div');
for (var i = 0; i < slides.length; i++) {
slides[i].className = '';
slides[i].classList.add(i > 1? 'after' : (i ? 'current':'before'))
}
},
init : function() {
theSlider.reset();
theSlider.track.classList.remove('not-loaded')
},
next : function() {
theSlider.track.classList.add('go-right');
setTimeout(function(){
var firstSlide = document.querySelector('.tract-slider-wrapp > div:first-child');
theSlider.track.appendChild(firstSlide);
theSlider.reset();
theSlider.track.classList.remove('go-right')
},theSlider.duration)
},
prev : function() {
theSlider.track.classList.add('go-left');
setTimeout(function() {
var lastSlide = document.querySelector('.tract-slider-wrapp > div:last-child');
theSlider.track.insertBefore(lastSlide, theSlider.track.firstChild);
theSlider.reset();
theSlider.track.classList.remove('go-left')
},theSlider.duration)
},
prevButton : document.querySelector('.slide-left'),
nextButton : document.querySelector('.slide-right')
};
window.addEventListener("load", theSlider.init);
theSlider.prevButton.addEventListener('click', theSlider.prev);
theSlider.nextButton.addEventListener('click', theSlider.next);
.tract-slider {
width: 100%;
height: 75vh;
position: relative;
overflow: hidden;
background-color: #f5f5f5;
}
.tract-slider-wrapp {
height: 100%;
transition: all 350ms cubic-bezier(.08, .13, 0, .81);
opacity: 1;
}
.tract-slider-wrapp.not-loaded {
opacity: 0;
}
.tract-slider-wrapp>div {
height: 100%;
position: absolute;
background: transparent no-repeat 50% 50% /cover;
width: 100%;
}
.tract-slider-wrapp > div.before {
margin-left: -100%;
}
.tract-slider-wrapp > div.current + div {
margin-left: 100%;
}
.tract-slider-wrapp > div.after ~ div {
opacity: 0;
}
.tract-slider-control {
position: absolute;
width: 100%;
z-index: 1;
top: 50%;
display: flex;
justify-content: space-between;
transform: translateY(-50%);
}
.tract-slider-control div {
background-color: rgba(0,0,0,.35);
padding: .5rem 1rem;
color: white;
cursor: pointer;
}
.tract-slider-control :first-child {
border-radius: 0 17px 17px 0;
}
.tract-slider-control :last-child {
border-radius: 17px 0 0 17px;
}
body {
margin: 0;
}
.go-right div {
transform: translateX(-100%);
}
.go-left div {
transform: translateX(100%);
}
.go-right div, .go-left div {
transition-property: transform;
transition-timing-function: cubic-bezier(.4,0,.2,1);
/* has to match `duration` in js: */
transition-duration: 600ms;
}
<div class="tract-slider">
<div class="tract-slider-wrapp not-loaded">
<div style="background-image:url('https://static.pexels.com/photos/126282/pexels-photo-126282.jpeg')"></div>
<div style="background-image:url('https://static.pexels.com/photos/29017/pexels-photo-29017.jpg')"></div>
<div style="background-image:url('https://static.pexels.com/photos/70760/dandelion-dandelion-seeds-taraxacum-fluffy-70760.jpeg')"></div>
</div>
<div class="tract-slider-control">
<div class="tract-slider-btn slide-left">Prev</div>
<div class="tract-slider-btn slide-right">Next</div>
</div>
</div>
If you want to change the animation duration you need to change it in both js and css.
The only current limitation is it needs at least 3 slides to work. I guess it could be adjusted to work with only two slides by: cloning the "inactive" slide into third position, removing the clone after transition and cloning the other one.
ToDo's:
prefix CSS so it works in more browsers
replace .classList.add('whatever') with .className += ' whatever' and
.classList.remove('whatever') with .className.replace('whatever', '') if you want to show IE some love.
I told the above just to tell you this: if you want to get going, don't reinvent the wheel.
It's great you use vanilla javascript. But sooner or later you'll end up writing your own wrappers for common things. Depending on how good you are/have become, you'll write your own, limited, custom version of jQuery. Allow me to put things into perspective: Google included a lite version of jQuery into AngularJS. It's that good.
You, as an single developer, do not stand a chance at writing a better, more streamlined and tested version of it. And besides, you don't have to. Use your skill and abilities to go forward, not sideways.

Related

CSS Animation - Steps - to Animate Bookmark Star

I have the following code:
let animation = document.getElementById('fave');
animation.addEventListener('click', function() {
$(animation).toggleClass('animate');
});
.fave {
width: 70px;
height: 50px;
position: relative;
overflow: hidden;
}
.fave img {
position: absolute;
top: 0;
left: 0;
cursor: pointer;
animation: test_animate_reverse 1s steps(55);
}
.fave .animate {
animation: test_animate 1s steps(55);
left: -3519px;
}
#keyframes test_animate {
from {left: 0;}
to {left: -3519px;}
}
#keyframes test_animate_reverse {
from {left: -3519px;}
to {left: 0;}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section class="fave"><img src="https://cssanimation.rocks/images/posts/steps/twitter_fave_rectangle.png" id="fave"></section>
The target image is: https://cssanimation.rocks/images/posts/steps/twitter_fave_rectangle.png (albeit already modified so that all the images are positioned horizontally).
The result is quite satisfactory already. However, I have concerns:
As can probably be seen, my star always animates from the last frame of said image to the first frame whenever I refresh the browser window. If possible, I'd like it to not do that when I first refresh the window and only reverse-animate when I toggle it from 'active' to 'not active'.
I feel like using two #keyframes just to reverse an animation that is exactly the same is kind of inefficient. Is there a way to achieve the same effect without having to make an additional reverse #keyframes?
Is there a way for me to achieve the same effect without specifying the size of section explicitly when said section does not have a parent?
When I click quickly a few times on said image, if possible, I'd like it to finish its current animation first before proceeding to the next one. With my code now, preceding animations are immediately ended when a new animation is run.
EDIT
I've tried to not use the reverse #keyframes by changing to the following:
.fave img {
position: absolute;
top: 0;
left: 0;
cursor: pointer;
animation: test_animate .7s steps(55);
animation-direction: reverse;
}
What happened is the animation completely vanished.
Why not use the code from the actual tutorial where you got the image. It uses transition rather than animation and seems neater.
It will automatically reverse the animation too with the transition applied to the element.
You can set a disabled flag and use setTimeout() to prevent multiple clicks before the animation has finished.
var click_disabled = false;
$('.fave').click(function() {
if (click_disabled) {
return; // do nothing
}
$(this).toggleClass('faved');
// Set correct aria-label
var label = $(this).attr('aria-label') == 'Favourite' ? 'Unfavourite' : 'Favourite';
$(this).attr('aria-label',label);
click_disabled = true;
// Timeout value should match transition length
setTimeout(function(){
click_disabled = false;
}, 1000);
});
.fave {
background: none;
border: 0;
padding: 0;
width: 70px;
height: 45px;
background: url(https://res.cloudinary.com/shanomurphy/image/upload/v1547543273/fave_ltre0q.png) no-repeat;
background-position: 0 0;
transition: background 1s steps(55);
outline: 0;
cursor: pointer;
}
.fave.faved {
background-position: -3519px 0;
}
<button class="fave" aria-label="Favourite"></button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Issue with adding class when element is in the viewport

I'm using Stick-Kit to keep some images in place while scrolling, and it seems to be affecting another script that initiates a CSS animation by adding a class to a div when it enters the viewport. I assume the Sticky-Kit script is 'reseting' the other, as the animation only occurs once when Sticky-Kit is removed. The issue is visible when the animated div gets to the top of the screen. How do I ensure the animation occurs only one time (when it first appears in the viewport)?
http://codepen.io/SeanLindsay1/pen/ZBVyLZ
HTML
<div id="bg">
<h2 class="header-title"><span>HEADER</span></h2>
<div id="pic1">
1
</div>
<div id="pic2">
2
</div>
<div id="pic3">
3
</div>
</div>
CSS
/* STICKY-KIT */
#bg {
background-color: white;
width:100%;
height:1500px;
padding:0;
margin:0;
font-size:30px
}
#pic1 {
position:relative;
width:60% ;
height:500px;
background-color:blue;
}
#pic2 {
position:relative;
width:60% ;
height:500px;
background-color:green;
}
#pic3 {
position:relative;
width:60% ;
height:500px;
background-color:red;
}
/* HEADER TITLES */
.header-title span {
display: inline-block;
position: relative;
}
.change:after {
content: " ";
position: absolute;
height: 5px;
border-bottom: 1px solid #bebebe;
-webkit-animation: extend .1s 1 forwards;
animation: extend 1s 1 forwards;
margin-left: 4px;
top: 1.2em !important;
}
#-webkit-keyframes extend {
0% {
width: 0;
}
100% {
width: 200px;
}
}
#keyframes extend {
0% {
width: 0;
}
100% {
width: 200px;
}
}
jQuery
// Check to see if element is in viewport
function isElementInViewport(elem) {
var $elem = jQuery(elem);
// Get the scroll position of the page.
var scrollElem = ((navigator.userAgent.toLowerCase().indexOf('webkit') != -1) ? 'body' : 'html');
var viewportTop = jQuery(scrollElem).scrollTop();
var viewportBottom = viewportTop + jQuery(window).height();
// Get the position of the element on the page.
var elemTop = Math.round( $elem.offset().top ) + 200 ;
var elemBottom = elemTop + $elem.height();
return ((elemTop < viewportBottom) && (elemBottom > viewportTop));
}
// Check if it's time to start the animation
function extendLine() {
var $elem = jQuery('.header-title span').each(function() {
var $elem = jQuery(this);
// If the animation has already been started
if ($elem.hasClass('change')) return;
if (isElementInViewport($elem)) {
// Start the animation
$elem.addClass('change');
}
});
}
// Capture scroll events
jQuery(window).scroll(function(){
extendLine();
});
$("#bg").stick_in_parent();
$("#text").stick_in_parent({offset_top: 390});
$("#pic1").stick_in_parent();
$("#pic2").stick_in_parent();
$("#pic3").stick_in_parent();
If possible, you can use a CSS Transition instead of an Animation. It'll have better browser support, and will work. I can't really find out what's happening in your code, but if you change a couple of lines, it'll work as expected.
Here is a forked codepen: http://codepen.io/ddanielbee/pen/BQbQqj
Here are the specific lines:
.header-title span::after {
content: " ";
transition: all 1.5s ease-out;
width: 0;
}
.header-title span.change::after {
position: absolute;
height: 5px;
border-bottom: 1px solid #bebebe;
width: 200px;
margin-left: 4px;
top: 1.2em !important;
}
Removing this line of code:
$("#bg").stick_in_parent();
ensures that the header text block is not influenced by Stick-Kit and eliminates the problem of repeated execution of the animation, as shown in this codepen.
I haven't observed any ill effects caused by that change, but I cannot guarantee that there aren't any, since I don't know why this line was in the original code.

How do I make a div go left and then right in javascript? (no jQuery)

This should be simple but I guess no jQuery makes it a bit difficult.
I want to repeat a process where a div goes 100px to the right (with animation) and then 100px to the left (so i want a continuous movement).
There seems to be plenty of jQuery answers to this question yet no pure javascript solution. I'm probably missing something obvious here yet I can't find it.
Here is the code:
var left = 0;
var id = setInterval(function(){goRight()}, 10);
var ed = setInterval(function(){goLeft()}, 10);
function goRight(){
var redpixel = document.getElementById("redpixel");
left++;
redpixel.style.left = left + "px";
if (left>100) {
clearInterval(id)
goLeft();
}
}
function goLeft(){
var redpixel = document.getElementById("redpixel");
left-=1;
redpixel.style.left = left + "px";
if (left<100) {
clearInterval(ed);
goRight()
}
}
HTML:
<button onclick="goRight()">Go Right</button>
<div id="redpixel"></div>
CSS:
.container {
width: 480px;
height: 800px;
border: 1px solid black;
}
#redpixel {
position: absolute;
top: 200px;
left: 0;
background: red;
width: 25px;
height: 25px;
}
Last comments:
The animation starts without me calling any function (without using the button), how is that possible?
The animation works but stops when it hits the first 100px.
(Additional question) - if i put the var redpixel out of the function it doesn't work at all, why?
All help appreciated, thanks!
The problem with your code is that you set left and right animations at the same time, and the left one is cleared immediately because left<100. Fixed code:
var left = 0,
id = setInterval(goRight, 10);
ed;
function goRight() {
var redpixel = document.getElementById("redpixel");
left++;
redpixel.style.left = left + "px";
if (left > 100) {
clearInterval(id);
ed = setInterval(goLeft, 10);
}
}
function goLeft() {
var redpixel = document.getElementById("redpixel");
left -= 1;
redpixel.style.left = left + "px";
if (left < 1) {
clearInterval(ed);
id = setInterval(goRight, 10);
}
}
#redpixel {
position: absolute;
top: 50px;
left: 0;
background: red;
width: 25px;
height: 25px;
}
<div id="redpixel"></div>
One more point, is as demonstrated by Adjit it really makes sense to look at CSS approach as simpler and more effective.
You don't need any JavaScript at all actually, and it is quite simple to do with CSS3.
Just need to set up keyframes and animation like so: (obviously including the necessary browser compatibility)
#box {
height: 100px;
width: 100px;
background: red;
position: relative;
animation: waver 2s infinite;
-webkit-animation: waver 2s infinite;
}
#keyframes waver {
0% {left: 0px;}
50% {left: 100px;}
100% {left: 0px;}
}
#-webkit-keyframes waver {
0% {left: 0px;}
50% {left: 100px;}
100% {left: 0px;}
}
See this fiddle for an example: http://jsfiddle.net/bwsd3eoy/

Pure JavaScript horizontal slider

Hello I am working on a slideshow with thumbnails.
My slideshow works but I'd like to have a horizontal slide of my thumbnail since I don't have enough space to display them all.
It could be on hover or on click of a button prev/next.
My code needs to be in javascript only no librairies.
Here is where I'm at, the entire code is in one page.
-- EDIT
Here is my HTML
<div class="controls">
<a class="previous" href=""><img src="images/fleche_g.jpg" alt=""></a>
<div class="thumbs">
<ul id="thumbs">
</ul>
</div>
<a class="next" href=""><img src="images/fleche_d.jpg" alt=""></a>
</div>
My CSS
.controls {
width: 658px;
height: 76px;
margin-top: 10px;
}
#thumbs {
display: inline-block;
overflow: hidden;
height: 76px;
position: relative;
z-index: 99;
}
.controls .previous, .controls .next {
float: left;
width: 51px;
height: 76px;
position: relative;
z-index: 999;
}
.controls .previous {
background: transparent url('images/fleche_g.jpg') 0 0 no-repeat;
}
.controls .next {
background: transparent url('images/fleche_d.jpg') 0 0 no-repeat;
}
And the 2 simple functions I have tried calling onClick of the a prev/next button.
// Move thumbs to the left
function left() {
document.getElementById('thumbs').style.left = '-124px';
}
// Move thumbs to the right
function right() {
document.getElementById('thumbs').style.right = '-124px';
}
So far nothing works at all. What am I missing?
I think this is what you need to solve your problem. 1 new function getLeft(); the rest is adaptions of existing functions and css.
I think this ought to work for more images, so the client has to push the right-button multiple times to get to the end.
You might also want extra calculations to restrict the range; so all the images are not beyond the right or left of the .wrapper-thumbs (feel free to ask me).
Notice: maybe you want left and right to do the opposite (I've seen both) ; just swap the 500 <-> -500
Or do you only want as many images so that only 1 push of the button gets you totally left or right?
css:
#slideshow .controls .wrapper-thumbs {
overflow: hidden;
position: relative; /* this means: the upper-left corner of <div class="wrapper-thumbs"> is now the the new base/zero (for style.left) for all the children with position: absolute */
float: left; /* this is so the left- and right-button are kept in place */
margin: 0;
width: 556px;
height: 76px;
}
#thumbs {
display: inline-block;
width: 900px;
height: 76px;
position: absolute; /* So now, the new base of #thumbs is set to the base/zero of <div class="wrapper-thumbs"> */
z-index: 99;
}
script:
// function reads style.left; removes 'px'; returns the numeric value.
function getLeft() {
var left = document.getElementById('thumbs').style.left;
return Number(left.substr(0, left.length - 2)); // .substr removes the 'px'
}
// Move thumbs to the left
function left() {
var currentLeft = getLeft();
var newLeft = currentLeft -500;
document.getElementById('thumbs').style.left = newLeft + 'px';
}
// Move thumbs to the right
function right() {
var currentLeft = getLeft();
var newLeft = currentLeft +500;
document.getElementById('thumbs').style.left = newLeft + 'px';
}

Firefox Scrolling Bug When Changing :after Contents

I want to change pseudo elements on a container based on its scroll position. I made a jsfiddle to demonstrate the bug I stumbled upon: http://jsfiddle.net/krzncu1k/1/
The :after-element shows the current scroll status ("upper half" or "lower half"). Its content changes by toggling the classes .upper-half and .lower-half based on scroll position:
.upper-half:after {content:'upper half'; top:0;}
.lower-half:after {content:'lower half'; bottom:0;}
The corresponding JS:
$wrap.toggleClass('upper-half', isUpper).toggleClass('lower-half', !isUpper);
The bug happens when using Firefox and scrolling via dragging the scrollbar (not via mousewheel!). If you drag it and cross the middle (where the class changes from .upper-half to .lower-half) you suddenly can't drag any further.
Any ideas on why this behavior occurs and how to fix it?
No idea why this occurs in Firefox, but I do have a workaround. Create the upper half with :before, the lower half with :after and hide and show with opacity when the class changes. Might as well throw in a smooth transition as well. The bug is prevented because the position is not changing.
(you could also use display: none instead of opacity, but it cannot be transitioned)
Working Example
$(function() {
var $wrap = $('.wrap'),
$ul = $('ul'),
ulHeight = $ul.height();
$ul.scroll(function(e) {
var isUpper = (this.scrollTop + ulHeight / 2) / this.scrollHeight <= 0.5;
$wrap.toggleClass('upper-half', isUpper).toggleClass('lower-half', !isUpper);
});
});
ul {
overflow: auto;
max-height: 200px;
background-color: #ddd;
}
li {
font-size: 200%;
}
.wrap {
position: relative;
width: 200px;
}
.wrap:before,
.wrap:after {
position: absolute;
left: 0;
width: 100px;
height: 40px;
background-color: rgba(128, 128, 128, 0.3);
text-align: center;
color: white;
line-height: 40px;
transition: opacity 0.5s;
}
.wrap:before {
content: 'upper half';
top: 0;
}
.wrap:after {
content: 'lower half';
bottom: 0;
}
.upper-half:after {
opacity: 0;
}
.lower-half:before {
opacity: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div class="wrap upper-half">
<ul>
<li>Some</li>
<li>Random</li>
<li>Bullet</li>
<li>Points</li>
<li>Just</li>
<li>To</li>
<li>Make</li>
<li>This</li>
<li>Awesome</li>
<li>List</li>
<li>A</li>
<li>Little</li>
<li>Longer</li>
<li>And</li>
<li>Longer</li>
<li>And</li>
<li>Longer</li>
</ul>
</div>

Categories

Resources