I'm having trouble figuring out why my CSS transition isn't firing when I programmatically remove a class from an element.
Essentially, I am trying to create a progressively enhanced, infinitely-scrolling carousel using pure Javascript.
The basic idea is that as the user clicks to scroll either left or right, the first or last element is plucked from the parent ul element, and either prepended or appended to the proper location, depending on the scroll-direction of the user's click.
Here is the relevant code:
HTML:
<div id="scroller">
<ul>
<li>
<a>
<img src="...">
</a>
</li>
..... more li elements
<li>
<a>
<img src="...">
</a>
</li>
</ul>
</div>
CSS:
#scroller {
position: absolute;
left: 0em;
height: 8em;
width: 400em;
}
#scroller ul {
list-style: none;
}
#scroller ul li {
overflow: hidden;
float: left;
height: 8em;
width: 8em;
transition: width 1s ease;
}
#scroller ul li.hide {
width: 0em;
}
#scroller ul li a img {
width: 8em;
}
JS (scroll right click event, for example):
/** Remove the element from the end of the list, add the hide class */
var node = this.list.removeChild(this.items[(this.items.length - 1)]);
/** Add the hide class to the node */
node.className += ' hide';
/** Insert the node at the beginning of the scroller */
this.list.insertBefore(node, this.items[0]);
/** Remove the hide class to trigger the transition animation */
node.className = node.className.replace('hide', '');
Everything is working well, in terms of the items being shifted around the ul correctly, so that is not the problem.
The issue is that the CSS transition is not being applied when the width of the li elements are changed by removing the "hide" class.
I had hoped to create a smooth scrolling effect in browsers than can support the CSS transition.
Thanks in advance for not suggesting that I use a JS library! :)
Use a combo of setTimeout and the transitionend event.
Look here for more info on transitionend:
CSS3 transition events
/** Remove the element from the end of the list, add the hide class */
one = document.getElementById('one');
two = document.getElementById('two');
list = document.getElementById('list');
/** Add the hide class to the node */
two.addEventListener('transitionend', end, false);
setTimeout(function(){
two.className += ' hide';
}, 0)
function end(){
/** Insert the node at the beginning of the scroller */
list.insertBefore(two, one);
/** Remove the hide class to trigger the transition animation */
setTimeout(function(){
two.className = two.className.replace('hide', '');
}, 0)
}
#scroller {
position: absolute;
left: 0em;
height: 8em;
width: 400em;
}
#scroller ul {
list-style: none;
}
#scroller ul li {
overflow: hidden;
float: left;
height: 8em;
width: 8em;
transition: width 1s ease;
}
#scroller ul li.hide {
width: 0em;
}
#scroller ul li a {
width: 8em;
background-color:red;
}
<div id="scroller">
<ul id="list">
<li id="one">
<a>
One
</a>
</li>
<li id="two">
<a>
Two
</a>
</li>
</ul>
</div>
Related
I'd spinning this off this original post: https://stackoverflow.com/a/65040903/1406440
I used something similar years ago (https://codepen.io/moy/pen/pZdjMX) but it uses a lot of jQuery in comparison. So I was wondering if I can amend the example in the original post to achieve the same effect. Which would mean...
Updating so that if there was an 'active' item, the line is already visible and moves from that location.
Is it possible to apply to a class rather than id?
With the above in mind, could this be applied in two separate places or would I need to duplicate the code?
Finally I wonder if we could use a :before or :after class so I don't have a div floating in the ul?
const navBar = document.getElementById("nav");
const navCursor = navBar.querySelector('.cursor');
const navItems = navBar.querySelectorAll('li');
function handleMouseEnterNavItem(event) {
// executed when mouse enter a navigation item
// update cursor to match position and size of target
const { offsetLeft, clientWidth } = event.target;
navCursor.style.left = offsetLeft + 'px';
navCursor.style.width = clientWidth + 'px';
}
navItems.forEach((navItem) => {
navItem.addEventListener('mouseenter', handleMouseEnterNavItem);
});
ul {
position: relative;
}
ul li {
font-family: sans-serif;
text-decoration: none;
color: gray;
margin: 22px 10px 10px;
display: inline-block;
padding: 0; /* note: padding will be underlined */
}
ul .cursor {
background: blue;
height: 1px;
position: absolute;
bottom: 0;
z-index: 10;
transition: .16s all 0.025s;
}
ul a {
text-decoration: none;
}
<ul id="nav" class="tabs-nav">
<li class="active">News</li>
<li>Activities</li>
<li>Search</li>
<li>Time</li>
<div class="cursor"></div>
</ul>
Trying to do a regular horizontal nav with arrow that has a endless loop. Tried figuring it out and some how I can't get it to loop back to "all" div.
tried slick carousel but it has this glitch when the page loads
HTML
<div id="second-navigation">
<div id="slider">
<li>All</li>
<li>slide1</li>
<li>slide2</li>
</div>
</div>
JS
var W = $('#second-navigation').width();
var N = $('#slider li').length;
var C = 0;
$('#slider').width( W*N );
$('#left, #right').click(function(){
var myID = this.id=='right' ? C++ : C-- ;
C= C===-1 ? N-1 : C%N ;
$('#slider').stop().animate({left: -C*W }, 1000 );
});
CSS
#second-navigation{
width:300px;
height:150px;
position:relative;
overflow:hidden;
}
#slider{
position:absolute;
height:150px;
left:0;
width:9999px;
}
#slider li{
float:left;
}
One approach would be to as follows:
When a direction button (ie left or right) is clicked, clone the list item that will be moved out of view by the animation (ie the first <li> for the <ul> when the "left" button is clicked)
attach the cloned <li> to the opposite end of the <ul> list (ie append the cloned first to the end of <ul> when "left" is clicked
animate the margin of the <ul> element to hide the original <li> that was cloned (ie for the "left" case, animate the <ul> left margin to "-150px", which is the width of your <li> items)
On completion of the animation, ensure the <ul> margin is reset, and that the now hidden <li> element has been removed
This can be written in code as shown below. There are a few extra things going on here:
The <ul> needs to be the direct parent of <li> items (a <div> cannot parent <li> items)
Flex box is used to achieve horizontal layout of <li> items, which simplifies CSS styling
Compounded animations need to be prevented (ie the case where button click happens during an existing animation). Below, I'm just ignoring clicks if an animation is underway
$("#left").click(function() {
/* If currently animated, early exit */
if($("#second-navigation ul:animated").length) {
return;
}
/* Clone the element that will be hidden */
var firstClone = $("li:first-child").clone();
$("#second-navigation ul")
.animate({
/* Animate ul container left margin to left */
"marginLeft": "-150px"
}, {
done: function() {
/* Reset ul container left margin */
$(this).css("marginLeft", "0px");
/* Remove element that is now hidden */
$("li:first-child", "#second-navigation ul").remove();
}
})
/* Add cloned element to end of list */
.append(firstClone);
});
$("#right").click(function() {
if($("#second-navigation ul:animated").length) {
return;
}
var lastClone = $("li:last-child").clone();
$("ul")
.css({
"marginLeft": "-150px"
})
.animate({
"marginLeft": "0"
}, {
done: function() {
$("li:last-child", "#second-navigation ul").remove();
}
})
.prepend(lastClone);
});
#second-navigation {
position: relative;
overflow: hidden;
width: 300px;
height: 150px;
}
ul {
display: flex;
width: fit-content;
flex-direction: row;
padding: 0;
margin: 0;
}
li {
position: relative;
height: 150px;
width: 150px;
top: 0;
left: 0;
transition: left ease-in-out 1s;
background: linear-gradient(45deg, black, transparent);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<div id="second-navigation">
<ul>
<li>All</li>
<li>slide1</li>
<li>slide2</li>
</ul>
</div>
<button id="left">Move left</button>
<button id="right">Move right</button>
Hope this helps!
I have a menu with a list of items created dynamically using javascript.
They have different colour and country attributes created using setAttribute.
$("#menuList a").hover(
function() {
var countryName = $(this).attr('country');
var fruitColour = $(this).attr('colour');
$('#toshow').append($("countryName \n fruitColour"));
},
function() {}
);
.toshow {
display: none;
}
#menuList a:hover div.toshow {
top: 0;
right: 0;
display: block;
position: absolute;
z-index: 99999;
background: red;
width: 200px;
height: 100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul class="menubar" id="menuList">
<li>Watermelon</li>
<li>Grapes</li>
<li>Strawberry</li>
<li>Blueberry</li>
</ul>
<div class="toshow" id="toshow"></div>
Here, I want to have a separated hidden div (display at top right of the page or next to the menuList) that does not have any content until any of the <a> tag being hovered, and show its responding two attributes until no more mouse hovered.
The code does not have errors. But I don't see anything in red when the mouse hovered through the list. Is it possible to achieve what I am looking for?
You can use the mouseout event to hide the toshow div with hide as you leave a list element. And at each hover event, you can change the html of toshow to the values of the li element which the user is hovering over and use show to display it.
Also make sure you attach the event handlers after you've inserted the html of the dynamically generated list.:
function displayGeneratedList() {
$('#menuList').html(`
<li>Watermelon</li>
<li>Grapes</li>
<li>Strawberry</li>
<li>Blueberry</li>
`);
$("#menuList a").hover(function() {
var countryName = $(this).attr('country');
var fruitColour = $(this).attr('colour');
$('#toshow').html(`${countryName}<br>${fruitColour}`).show();
});
$('#menuList a').mouseout(function() {
$('#toshow').hide();
});
}
$(document).ready(function() {
displayGeneratedList();
});
#menuList {
display: inline-block;
}
.toshow {
display: none;
float: right;
background: maroon;
width: 200px;
height: 100px;
padding: 5px;
color: white
}
<ul class="menubar" id="menuList">
</ul>
<div class="toshow" id="toshow"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I have several lis with some text in them. The width is limited, so the user is able to see the first 30 characters. I'm trying to achieve two things, add ... after the first 30 characters (obviously ... should disappear in expanded mode), and the second thing is the ability to expand the li to show the whole text. Right now, the text is expanded by default, can't understand why.
fiddle here.
HTML:
<div>
<ul>
<li id="a">normal length text</li>
<li id="b">this is a relatively long text</li>
<li id="c">this is an example of a really long text</li>
<li id="d">this is an example of an extremely long, pointless text. this is an example of an extremely long, pointless text</li>
</ul>
</div>
CSS:
div {
width: 250px;
}
div ul {
text-align: left;
padding: 0 10px;
}
ul li {
list-style-position: inside;
overflow: hidden;
//text-overflow: elipsis;
}
JS:
$("li").on("click", function() {
var titleLength = $(this).context.innerHTML.length;
if (titleLength > 30) {
if ($(this).height() == 18) {
$(this).css('height', 'auto');
var height = $(this).height();
$(this).height(18).animate({height: height}, 200)
} else {
$(this).animate({
height: 18
}, 200)
}
}
});
You're not giving your li elements any restricted height by default, meaning they default to auto, thus encompass the entire string.
As your jQuery code sets their height to 18px when closed, simply add the following:
ul li {
height: 18px;
...
}
Modified JSFiddle.
On top of that:
You've spelled "ellipsis" incorrectly in your text-overflow property.
The text-overflow property will be ignored without specifying adding a white-space property with a value of nowrap.
After doing that, you'll need to remove and re-add the white-space property within your jQuery.
Here's another modified JSFiddle demo with all the above taken into consideration.
Firstly, the setting is ellipsis, not elipsis, note the double l. Also, you're not constraining the size of the li elements, so the setting would have no effect. You need to set a width on the elements, as well as stop the elements wrapping by default.
To then achieve the expanding logic you could toggle a class on click which undoes the CSS rules required to make the text-overflow work. Try this:
$("li").on("click", function() {
$(this).toggleClass('expand');
});
div {
width: 250px;
background-color: lightgrey;
}
div ul {
text-align: left;
padding: 0 10px;
}
ul li {
list-style-position: inside;
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
ul li.expand {
white-space: normal;
overflow: visible;
text-overflow: clip;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<ul>
<li id="a">normal length text</li>
<li id="b">this is a reletively long text</li>
<li id="c">this is an example of a really long text</li>
<li id="d">this is an example of an extremdsfsdfs sgdfdsfgdfg ertwerr wee www ely long, pointless text</li>
</ul>
</div>
There is no need javascript to add ... after your overflow li
ul li {
list-style-position: inside;
overflow: hidden;
text-overflow: elipsis;
white-space: nowrap;
text-overflow: ellipsis;
}
Then change the white-space attribute when click on li
$("li").on("click", function() {
$(this).css('white-space','normal');
});
Try it yourself https://jsfiddle.net/tcfLgom9/3/
Is it possible to trigger changes to CSS of an element that is completely unrelated to the hovered div?
I have a CSS hover effect on a dropdown menu, that I also want to trigger the opacity of a div right at the bottom of the page to create a background overlay effect.
This is the CSS I'm using:
#overlay {
background:rgba(0,0,0,0.5);
position:absolute;
top:120px;
left:0;
z-index:0;
height:120%;
width:100%;
visibility:hidden;
opacity:0;
}
#menu-main-menu li.menu-parent-item:hover ul.sub-menu,
#menu-main-menu li.menu-parent-item:hover #overlay {
visibility:visible;
opacity:1;
}
The hover of the sub menu works fine, but the div #overlay is right at the bottom of the page, and doesn't get called when it's hovered.
I've tried all kinds of alternatives such as :hover > #overlay, :hover + #overlay, but nothing seems to trigger it. I also can't seem to find a definitive answer to the question.
Is it possible?
Yes. You can load this style in a php file and then use jQuery to apply the css when your div has been hovered on.
No there is no way to select parent element in css and that means that you cannot move up in hierarchy.
<ul class="hover-parent">
<li></li>
</ul>
<div>Something here</div>
<div class="target"></div>
From this point :
.hover-parent li:hover you cannot go up (to ul or div).
Selectors which you tried to use are "next":
A>B - This will select only direct B children of A
A+B This will select B immediately preceded by A
Here you can find W3C documentation of CSS selector
http://www.w3.org/TR/CSS2/selector.html#adjacent-selectors
And demos:
http://code.tutsplus.com/tutorials/the-30-css-selectors-you-must-memorize--net-16048
Notice that it will be really confusing for user that different part off app/page is changing when he is hovering something else. Bad UX idea.
You're going to have to use JavaScript to do this.
Your posted selector #menu-main-menu li.menu-parent-item:hover #overlay is looking for #overlay somewhere inside of an ancestor element of li.menu-parent-item that is somewhere inside of an ancestor element with an id of #menu-main-menu.
Using the child selector > will not work as the overlay element is not a child of the list element you're hovering in your menu from what you have described and from comment responses.
As #Paulie_D has pointed out the two target elements, the element to be hovered and the overlay element, need to adjacent siblings to use the sibling selector +. From what you have described and the comment responses they are not adjacent siblings.
I have setup a basic example for you using jQuery. This example displays the overlay as long as you are hovering any element in the .main-menu element.
HTML
<ul class="main-menu">
<li>Item One</li>
<li>Item Two</li>
<li>Item Three
<ul class="sub-menu">
<li>Sub Item One</li>
<li>Sub Item Two</li>
<li>Sub Item Three</li>
<li>Sub Item Four</li>
<li>Sub Item Five</li>
</ul>
</li>
</ul>
<main>
Content here.
</main>
<footer>
<div class="overlay">This is my overlay.</div>
</footer>
CSS
body {
margin: 25px auto;
width: 500px;
}
ul,
li {
margin: 0;
padding: 0;
}
main {
min-height: 300px;
}
footer,
.overlay {
height: 50px;
}
footer {
position: realative;
background-color: yellow;
}
.main-menu {
list-style: none;
height: 50px;
}
.main-menu > li {
float: left;
padding: 0 10px;
position: relative;
}
.main-menu > li:hover .sub-menu {
display: block;
}
.sub-menu {
display: none;
list-style: none;
position: absolute;
left: 10px;
width: 150px;
}
.overlay {
display: none;
text-align: center;
}
jQuery
$overlay = $('.overlay');
$('.main-menu > li').hover(
// when hovered
function() {
$overlay.css('display','block');
},
// when NOT hovered
function() {
$overlay.css('display','none');
}
);
jsFiddle: http://jsfiddle.net/ednf2pzq/
Edit
You could simplify the jQuery hover selector to .main-menu.
jQuery
$('.main-menu > li').hover(
// same code as before
);
jsFiddle: http://jsfiddle.net/ednf2pzq/1/