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>
Related
I've noticed on the mobile version (iOS) of my website that the main navigation requires links to be tapped twice for the page to redirect. After removing various styles/bits of code I found the cause of the problem, it's my Javascript for a 'sliding line' hover effect.
My basic understanding would be that as the script is still running on mobile, when it's not really needed, it means the navigation is running/expecting a hover effect and once that's run you can then click a link as you intend?
The script works perfect on desktop, so I don't want to change any of the functionality but is there something I can add to prevent this bug on mobile devices? Alternatively, would a javascript 'media query' type thing, stopping the script from running below 1000px be a better solution? If so what would be the best way to implement that?
Thank in advance!
CodePen: https://codepen.io/moy/pen/pZdjMX
$(function() {
var $el,
leftPos,
newWidth,
$mainNav = $(".site-nav__list");
$mainNav.append("<div class='site-nav__line'></div>");
var $magicLine = $(".site-nav__line"),
$currentMenu = $(".current-menu-item");
$magicLine
.width($currentMenu.length ? $currentMenu.width() : 0)
.css("left", $currentMenu.length ? $currentMenu.find("a").position().left : 0)
.data("origLeft", $magicLine.position().left)
.data("origWidth", $magicLine.width());
var hoverOut;
$(".site-nav__list li a").hover(function() {
clearTimeout(hoverOut);
$el = $(this);
leftPos = $el.position().left;
newWidth = $el.parent().width();
if (!$magicLine.width()) {
$magicLine.stop().hide().css({
left: leftPos,
width: newWidth
}).fadeIn(100);
} else {
$magicLine.stop().animate({
opacity: 1,
left: leftPos,
width: newWidth
});
}
},
function() {
hoverOut = setTimeout(function() {
if (!$currentMenu.length) {
$magicLine.fadeOut(100, function() {
$magicLine.css({
left: $magicLine.data("origLeft"),
width: $magicLine.data("origWidth")
});
});
} else {
$magicLine.stop().animate({
left: $magicLine.data("origLeft"),
width: $magicLine.data("origWidth")
});
}
}, 100);
}
);
});
/* Header */
.page-head {
background: white;
border-top: 2px solid #ddd;
box-sizing: border-box;
overflow: hidden;
padding: 0 30px;
position: relative;
width: 100%;
}
.page-head__logo {
background-image: none;
float: left;
padding: 0;
text-shadow: none;
width: 200px;
}
/* Nav */
.site-nav {
display: block;
float: right;
text-align: center;
width: auto;
}
.site-nav__list {
list-style: none;
margin: 0;
padding: 0;
position: relative;
top: auto;
left: auto;
width: auto;
}
.site-nav__list li {
background: none;
display: block;
float: left;
margin: 0;
padding-left: 0;
text-transform: uppercase;
}
.site-nav__list a {
box-sizing: border-box;
display: block;
font-weight: 900;
padding: 30px 15px;
transition: color .15s;
text-shadow: none;
}
.site-nav__list a {
color: red;
}
/* Underline */
.site-nav__line {
background: red;
content: "";
display: block;
height: 2px;
position: absolute;
top: -2px;
left: 0;
width: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<header class="page-head">
Logo Here
<nav class="site-nav ">
<ul class="site-nav__list">
<li class="site-nav__item ">About</li>
<li class="site-nav__item">Looooonger Title</li>
<li class="site-nav__item">Company</li>
<li class="site-nav__item">About</li>
<li class="site-nav__item">Login</li>
<li class="site-nav__item">Apply</li>
</ul>
</nav>
</header>
</body>
if your problem is you to double click it before redirecting to its page, Try thi
$('.site-nav__list a').click(function(){
$(this).click();
});
the function is when you click the navigation the script will click it again,
If you're sure that the cause of the problem is running that script on mobile screens, you can call sliding script only on desktops with this code:
if ( $(window).width() > 739) {
//Desktop scripts
}
else {
//mobile scripts
}
You can change the screen width of devices you want to script work on them by changing 739. After that your script will run only on screens larger that 739px or what you've choose.
Update
If you want to everything works correct after resizing, You should do a little trick.
Personally, I use this method because it's the only way that makes you sure about bugs and problems. The trick is reloading the page after resizing.
It's not costly in many cases because most of the things cashed and don't need to redownloading. There are lots of methods to do that, but I use the below one because it works good and is simple and short:
window.onresize = function () {
location = location;
}
You just need to add this lines at the end of your script file. After resizing, everything will work well again.
How it works?
When you resize the window, a javascript event will emit. What we done in the last code is overriding the event listener of that event. So when the user resize the window, the location = location; code will execute.
What this line means? the location object is a property of window object and keeping information about current window url. When you change the location of a window, browser page will reload to getting the new window of the new location (more info about location).
What we done here is assigning current location to the location. So browser thinks we had a redirect request and reloads the page. But because the new location is the same object as previous one, the page will reload instead of redirecting to somewhere else.
I have read a lot of the questions on here but can't find one that fixes this. I have programmed a div to follow my cursor. I only want it to appear when the cursor is over #backgroundiv. I have got it working but it sometimes randomly flickers on chrome and disappears entirely on firefox. Even more randomly is it sometimes appears to work and then starts flickering. I have tried a variety of things from hover to mouseenter/mouseover but nothing seems to work.
What I want is for #newdot to appear when the cursor is over #backgroundiv and then follow the cursor around the div. Any help would be much appreciated.
//hide dot when leaves the page
$(document).ready(function() {
$("#backgroundiv").hover(function() {
$("#newdot").removeClass("hide");
}, function() {
$("#newdot").addClass("hide");
});
});
//div follows the cursor
$("#backgroundiv").on('mousemove', function(e) {
//below centres the div
var newdotwidth = $("#newdot").width() / 2;
$('#newdot').css({
left: e.pageX - newdotwidth,
top: e.pageY - newdotwidth
});
});
//tried below too but it doesn't work
/*$(document).ready(function(){
$("#backgroundiv").mouseenter(function(){
$("#newdot").removeClass("hide");
});
$("#backgroundiv").mouseout(function(){
$("#newdot").addClass("hide");
});
}); */
#backgroundiv {
width: 400px;
height: 400px;
background-color: blue;
z-index: 1;
}
#newdot {
width: 40px;
height: 40px;
background-color: red;
position: absolute;
z-index: 2;
}
.hide {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="newdot"></div>
<div id="backgroundiv"></div>
There is not issue but a logical behavior, when you hover on the blue div you trigger mouseenter so you remove the class and you see the red one BUT when you hover the red one you trigger mouseleave from the blue div thus you add the class and you hide the red one. Now the red is hidden you trigger again the mouseenter on the blue div and you remove the class again and the red div is shown, and so on ... this is the flicker.
To avoid this you can consider the hover on the red box to make the red box appear on its hover when you lose the hover from the blue one.
$(document).ready(function() {
$("#backgroundiv").hover(function() {
$("#newdot").removeClass("hide");
}, function() {
$("#newdot").addClass("hide");
});
});
//div follows the cursor
$("#backgroundiv").on('mousemove', function(e) {
//below centres the div
var newdotwidth = $("#newdot").width() / 2;
$('#newdot').css({
left: e.pageX - newdotwidth,
top: e.pageY - newdotwidth
});
});
#backgroundiv {
width: 400px;
height: 400px;
background-color: blue;
z-index: 1;
}
#newdot {
width: 40px;
height: 40px;
background-color: red;
position: absolute;
z-index: 2;
}
.hide {
display: none;
}
/* Added this code */
#newdot:hover {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="newdot">
</div>
<div id="backgroundiv">
</div>
I have a foreach loop which displays a list of items using relative and absolute positioning, and on the bottom I would like to add a button (which is at the bottom of the container), which when pressed, shows/hides the given information, pushing the button with itself. I've looked at a couple of stackoverflow questions which had basically the same problem, but I couldn't find a solution which would work in my case.
Here are the codes for the problem (since I've tried a couple solutions, the style positions might not be logical, if you see anything weird please let me know):
The view:
<ul class="events>
#foreach (var events in Model)
{
//absolute positioned div-s
<li>
<div class="eventActions">
<button class="toggleBet">Place bet</button>
#Html.ActionLink("Event details", "Details", "Event", new { eventId = events.Id }, null)
<div class="betContent">#Html.Partial("_BetPartial", new BetViewModel(events))</div>
</div>
</li>
</ul>
The styles:
.events > li .eventActions {
position: absolute;
bottom: 0;
right: 0;
font-size: 24px;
height: 200px;
}
.events > li .toggleBet {
display: inline-block;
}
.events > li .betContent {
background-color: green;
margin: 0;
max-height: 0;
overflow: hidden;
transition: max-height 1s;
}
.events > li .eventActions.open .betContent {
max-height: 300px;
}
The jQuery:
$(".toggleBet").on("click",function(e) {
$(this.parentNode).toggleClass("open");
});
Here is a fiddle which shows what I would like to achieve: http://jsfiddle.net/yeyene/fpPJz/3/ (credits to user yeyene, from this question)
And here is the picture of my project so far (I would like to extend the list items height, move the links lower and make them move up when clicked)
Thank you in advance!
I would suggest forgetting about the .slideToggle method and just using a CSS class on the parent container, then use the max-height property to toggle between open and closed (or just height if you already know exactly how big the container should be when opened).
Here's a simple fiddle showing how you can do this with "pure" CSS by just adding a class to a container: https://jsfiddle.net/8ea3drce/
For good measure, below is the code used in the above JS fiddle:
HTML
<div class="container">
<a class="trigger">Trigger</a>
<ol class="content">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ol>
</div>
CSS
.container {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
}
.container .trigger {
display: inline-block;
background-color: red;
color: white;
cursor: pointer;
padding: 1em;
}
.container .content {
background-color: lightblue;
margin: 0;
max-height: 0; // This suppresses the element's height.
overflow: hidden; // This keeps internal elements from being visible when height is suppressed.
transition: max-height .5s; // This animates the motion when max-height is released. This isn't usually perfect. The closer max-height comes to be with the actual height of the element, the better. Fixed heights might be ideal.
}
.container.open .content {
max-height: 300px; // This releases the element's height to be as large as it would naturally be, up to 500px.
}
Javascript/jQuery
$('.trigger').on('click', function(e) {
$(this.parentNode).toggleClass('open');
})
Using the idea of classtoggling as shown in Dom's answer, setting the absolute position's anchors correctly and deleting the interfering height attribute solved the problem!
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.
I'm trying to gradually fade in the scroll-bar. Currently, how I am making the scroll-bar appear is by adding a class to the body that changes the overflow to auto, but it looks very jerky / abrupt.
Here is the JS code that abruptly adds the class that shows the scroll-bar:
var bodywidth = $('body').width();
var scrollwidth = 10;
$('body').mousemove(function(e){
var x = e.pageX - this.offsetLeft;
if(x>bodywidth-scrollwidth)
$('body').addClass("auto");
else
$('body').removeClass("auto");
});
And here is the CSS corresponding to those clases:
body
{
margin:0;
overflow:hidden;
}
.auto
{
overflow:auto;
}
How can I make this transition less abrupt? Is there a better way to do it that adding the class and removing the class.
The scrollbars can be customized via -webkit-scrollbar, but this can not be animated (or at least I didn't succeded at it), and support in other browser is poor.
An alternative is to set a div just over the scrollbar, make it the same color than the base div, and make it gradually transparent to show the scrollbar
the html is:
<div class="container">
<div class="base">
<p>aaa aaaaa aaaaa aaaaaaaa aaaa aaa aaa aaaa bbbbbb bbbbbb cccc cccc cccc
</p>
</div>
<div class="hide">
</div>
</div>
the CSS is:
.base {
width: 100px;
background-color: white;
top: 0px;
position: absolute;
height: 130px;
overflow: hidden;
padding-right: 20px;
}
.base.clipped {
overflow: auto;
}
.hide {
position: absolute;
width: 19px;
height: 100%;
right: 0px;
background-color: white;
top: 0px;
-webkit-transition: all 2s;
z-index: 10;
}
.hide.clipped {
background-color: transparent;
}
I am keeping the class of the elements all the time, but adding a second class clipped to both. I set a padding in the element that will have the scrollbars so that there space for it without rearranging the layout. The hide element can be transitioned with css, the overflow not.
the javascript is
$("*").click(function(){
var obj = $(".base");
var hid = $(".hide");
if (obj.hasClass("clipped")) {
hid.removeClass("clipped");
setTimeout(function() {
obj.removeClass("clipped");
}, 2000);
} else {
hid.addClass("clipped");
obj.addClass('clipped');
}
});
demo