I have a page where in the content area a dynamic number of rows are generated. Each row has two columns: a side block and a content area. The idea is to have the side block stick while the page scrolls down until the next block appears and pushes the block above away and become sticky itself. The html structure is as follows:
<div class="container">
<div class="row">
<div class="col-md-4 sticky">
<h2>Example</h2>
<p class="page-side-block">...</p>
...
</div>
<div class="col-md-8 col-md-offset-4">
<p>...</p>
</div>
</div>
...
<div class="row">
<div class="col-md-4 sticky">
<h2>Example</h2>
<p class="page-side-block">...</p>
...
</div>
<div class="col-md-8 col-md-offset-4">
<p>...</p>
</div>
</div>
</div>
I've managed to get the first block to stick but I don't really know how to get about fixing the functionality as described above. Any help on the matter would be greatly appreciated.
I'll include the javascript (using mootools) below. I know this won't work with the html that is provided above as I've edited it to show the semantics rather than the actual markup.
window.onscroll = function() {
var stickyBlock = $('sticky-block');
if (window.getScroll().y > 235) {
stickyBlock.setStyles({
position: 'fixed',
top: '100px',
width: "350px"
});
} else if (window.getScroll().y < 235) {
stickyBlock.setStyles({
position: 'absolute',
top: 0,
width: null
});
}
}
I know that in the new w3c specs a definition has been made for a position: sticky; property. This would make javascript completely unnecessary but so far browser support for it is horrible as shown here.
Again if anyone could help me out, that would be awesome ;)
A bit late night here, but leave you a suggestion.
If you capture all positions and listen to scroll you can iterate the initial positions and compare it with scroll and make the element fixed when needed.
In my example I use .pin() that you can find in MooTools More, just to not re-invent the wheel.
Example: http://jsfiddle.net/2AZ28/
MooTools:
var stickyBlock = $$('.sticky').map(function(st){
return {
el: st,
pos: st.getPosition().y,
height: st.getHeight()
};
});
window.addEvent('scroll', function(){
var currentScroll = window.getScroll().y;
stickyBlock.each(function (st, i) {
if (st.pos <= currentScroll){
st.el.pin();
if (stickyBlock[i + 1] && (stickyBlock[i + 1].pos - st.height <= currentScroll)) st.el.setStyle('top', stickyBlock[i + 1].pos - st.height - currentScroll);
} else {
st.el.unpin();
}
});
})
Maybe could be more optimized, but hope this helps in your question.
Related
What I'm trying to accomplish: When any element with the class "yes" is scrolled to a specific Y position in the window (we'll just pretend it's the very top of the window to keep it simple), I just want to get that element so I can grab some of its attributes.
<div class="container">
<div class="no"></div>
<div class="yes"></div>
<div class="yes"></div>
<div class="yes"></div>
<div class="yes"></div>
<div class="no"></div>
</div>
I've tried this two ways, and this first way works, but it seems terribly inefficient, as it's constantly have to assess the position all of the elements during scroll. There can be a lot. But maybe I'm overestimating how how inefficient it is?
Method 1:
$el = $('.yes');
$el.each(function(){
var distance = $(this).offset().top;
$window.scroll(function() {
if ( $window.scrollTop() >= distance ) {
// get the stuff
}
});
});
The other way seems more efficient (but maybe not?) but I haven't gotten it to work as needed:
$window.scroll(function(){
var el = document.elementsFromPoint(0, 0);
var $el = $(el);
if($el.hasClass('yes')){
//get the stuff
}
});
This doesn't seem to be working correctly at all. Picks up some child elements ".yes" element, even if those elements don't have the "yes" class themselves, and also fails to pick up other ".yes" elements at all. I don't know why.
(Side note: I've simplified the code examples to remove other efficiencies, such as setTimeout for scrolling and tests to only perform functions if the element I'm trying to get changes, etc… I just need to figure out how to get position testing to work best).
I think what you're looking for is the intersection/observer API
<div class="container">
<div class="no"></div>
<div lang="javascript" class="yes"></div>
<div class="no"></div>
<div lang="python" class="yes"></div>
<div class="no"></div>
<div lang="go" class="yes"></div>
<div class="no"></div>
<div lang="rust" class="yes"></div>
<div class="no"></div>
<div class="end"><h1>HOORAY! YOU KNOW KNOW THE INTERSECTION OBSERVER API</h1>
Read more about it here
</div>
</div>
<script>
let observer = new IntersectionObserver((divs, observer) => {
divs.forEach(div => {
if(div.isIntersecting){
//grab your attributes
var _target = div.target
let theAttr = _target.getAttribute("lang");
console.log('div is intersecting with language attribute of: ' + theAttr);
observer.unobserve(div.target);
}
});
}, {threshold: 1});
document.querySelectorAll('div.container > div.yes').forEach(div => { observer.observe(div) });
</script>
<style>
.container {height: 500vh;}
.no {background-color: #e87e7e;
height: 50vh;}
.yes {background-color: #00800478;
height: 50vh;}
.end{text-align: center;}
</style>
here is mozilla's documentation on the intersection/observer API.
I've searched a solution for my problem but I didn't find any answer for that.
here is the problem, I'm using animate.css for many DIVs but I want the animations take effect when the element -which has classes 'animated' and 'animation in animate.css'- is in viewport. My problem is that classes 'animated' and 'animation in animate.css' are added to all my DIV's whatever the position is.
the script
$(function() {
"use strict";
$(window).scroll(function() {
addClassToElementInViewport($('div.accueilText'), "slideInleft");
addClassToElementInViewport($('div.ateHT'), "slideInLeft");
});
function addClassToElementInViewport(element, newClass) {
if (isVisible(element)) {
element.removeClass('hidden');
element.addClass(newClass);
}
}
function isVisible($el) {
var winTop = $(window).scrollTop();
var winBottom = winTop + $(window).height();
var elTop = $el.offset().top;
var elBottom = elTop + $el.height();
return ((elBottom<= winBottom) && (elTop >= winTop));
}
});
html code
<div class="container">
<div class="row text-center accueilText hidden animated">
<div class="col-md-6 col-md-offset-3">blablabla bla bla<br>bla<br>blabla bla </div>
</div>
<hr>
<div class="row">
<div class="col-lg-12"><center><img class="img-responsive" src="Images/verin_hydroseb.png" alt="Hydroseb logo"></center></div>
</div>
</div>
<div class="row">
<div class="text-justify col-sm-5 ateHT hidden animated"> <br>bla bla blabllabla </div>
<div class="col-sm-5 col-sm-offset-2 ateHI hidden animated"> <img class="img-responsive" src="Images/atelier_hydraulique.png" alt="HydroSeb Hydraulique"></div>
</div>
I really appreciate any help you can provide :)
$('div.accueilText') this will find all divs with class accueilText
But i feel you may be looking for this.
First of all on-screen visible area is known as Viewport.
(image is taken from OP. Cleared and edited in Photoshop)
So all you need is to detect all elements in your Viewport.
This can be achieved using many plugins for jQuery, but I'll explain you on one example, which is called as jQuery withinviewport
Link to source and documentation on: [ withInViewport - Github ]
Step 1:
Download plugins and include jQuery framework and withinviewport plugin in your script:
<script src="http://code.jquery.com/jquery-1.7.min.js"></script>
<script src="withinViewport.js"></script>
<script src="jquery.withinviewport.js"></script>
.
Step 2:
Initialise function on scroll event:
$(window).bind("scroll", function() {
//your code placeholder
});
.
Step 3:
Use withinviewport selector to get all elements in you Viewport and by each element add class to corresponding list-item in your #timeline container:
$("#elements > div").withinviewport().each(function() {
$('#timeline > li[view-id="'+$(this)[0].id+'"]').addClass('active');
});
Step 4:
Put all together:
$(window).bind("scroll", function() {
//clear all active class
$('#timeline > li').removeClass('active');
//add active class to timeline
$("#elements > div").withinviewport().each(function() {
$('#timeline > li[view-id="'+$(this)[0].id+'"]').addClass('active');
});
});
.
.
Also this plugin gives you opportunity to set top, bottom, left and right offset for view-port.
See demo here: http://patik.com/code/within-viewport/
Well, i am stucked and can't find the answer myself. Hopefully someone can give me a hint.
I try to fullfill the following requirements:
There should be a Newsblock within a HTML Page with a fixed width and
height.
In this Newsblock only the title of the news are visible.
Those news are "collapsed" by default and should "expand" if the Mouse is over it.
Due the fact that the 'Newsblock' is limited by its height, there should be a Scrollbar visible. But only if the currently expanded news makes it necessary, so the user can Scroll down.
Newstitle and Newstext should never leave the Newsblock.
so far so good, i was able to fullfill all those demands except the one with the Scrollbar. If i try to reach the Scrollbar out of the currently expanded news it collapses again and the Scrollbar disappears. I understand that my .hover is configured that it always SlideUp if i leave the newsentry and the Scrollbar isn't a part of the newsentry div. But i have no idea what to change to still have an overall Scrollbar for the Newsblock, but won't disappear if i try to 'reach' it.
P.s.: A Scrollbar only per Newsentry looks weird. Thats why i want 'bind' the scrollbar to the parent container :S
HTML
<div id="newsblock">
<div> // some auto generated div's i have to life with, so the news entries are not 'direct' children of the newsblock.
<div class="newsentry">
<div class="newstitle">...</div>
<div class="newstext">...</div>
</div>
... another 9 'newsentry' divs.
</div>
</div>
JS
$(".newsentry").hover(
function() {
$(this).children(".newstext").stop(true,true).slideDown();
},
function() {
$(this).children(".newstext").stop(true,true).slideUp();
}
);
CSS
.newsblock {
height: 200px;
overflow-y: auto;
}
Instead of closing a .newsentry when the cursor goes out of it, a solution can be to close it only when it enters another .newsentry or when it leaves #newsblock.
The scrollbar being part of #newsblock, the entry isn't closed anymore when you go on it.
EDIT: Following our discussion about the scroll issue, I added a step callback to the closing animation to make sure that the top of the .newsentry getting opened remains visible when the other entries are getting closed.
Here is a working example:
var $newsblock = $("#newsblock");
function closeAllNews(slideUpArgs){
return $(".newstext").stop(true).slideUp(slideUpArgs);
}
function openNews(news, slideDownArgs){
$(news).find(".newstext").stop(true).slideDown(slideDownArgs);
}
function ensureNewsTopVisible(news){
// Check if the top of the newsentry is visible...
var top = $(news).position().top;
if(top < 0){
// ...and if not, scroll newsblock accordingly.
$newsblock.scrollTop($newsblock.scrollTop() + top);
}
}
$(".newsentry").each(function(){
var $this = $(this);
// When the mouse enter a news entry...
$this.on("mouseenter", function(){
// ...close all opened entries (normally there is at most one)...
closeAllNews({
// (while making sure that the top of this entry remains visible
// at each step)
step: ensureNewsTopVisible.bind(null, $this)
});
// ...open this newsentry.
openNews($this);
});
});
// When the mouse get out of the newsblock, close all news.
$newsblock.on("mouseleave", closeAllNews);
.newstitle {
font-size: 2em;
}
.newstext {
display: none;
}
#newsblock {
max-height: 150px;
overflow: scroll;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="newsblock">
<div>
<div class="newsentry">
<div class="newstitle">News 1</div>
<div class="newstext"></div>
</div>
<div class="newsentry">
<div class="newstitle">News 2</div>
<div class="newstext"></div>
</div>
<div class="newsentry">
<div class="newstitle">News 3</div>
<div class="newstext"></div>
</div>
<!-- Etc. -->
</div>
</div>
<!-- Ignore the script below. It is just filling in the news' text. -->
<script>
$(".newstext").each(function(i, newstext){
$.get("http://baconipsum.com/api/?type=meat-and-filler&format=html¶s=5&num=" + i)
.then(function(ipsumHtml){
$(newstext).html(ipsumHtml);
});
});
</script>
Try this:
$(".newsentry, .newsblock").hover( // <-- changed
function() {
$(this).children(".newstext").stop(true,true).slideDown();
},
function() {
$(this).children(".newstext").stop(true,true).slideUp();
}
);
This makes sure the block stays open when you hover either over the header or the block itself.
Is that what you mean?
There would be a joke , if i am wrong .. what i thing just change your css as
/* not .newsblock **/
#newsblock {
height: 200px;
overflow-y: scroll;/* not auto*/
}
It will be a lot better if you use click operation instead of hover to slide down news text block because the user can accidentally hover over any of the news entry in order to reach for the scroll bar. I think you need a accordion like functionality. You can use the below code if you are fine with click instead of hover.
$(".newsentry").click(
function() {
$(".newstext").stop(true,true).slideUp();
$(this).children(".newstext").stop(true,true).slideDown();
}
);
Or use the below one to go with hover.
$(".newsentry").hover(
function() {
$(".newstext").stop(true,true).slideUp();
$(this).children(".newstext").stop(true,true).slideDown();
},
function(){}
);
This will not close the news text block until you accidentally hover over another news entry.
I have a header bar with a search function on my site.
One of the functions I've implemented involves finding results and resizing the boxes that contain them.
One one page that has this header bar my function works perfectly:
$(".suggestionCategory").each(function() {
$(this).find(".suggestionCategoryName").outerHeight($(this).find(".suggestionCategoryValues").height());
});
But on another I get the following error message:
Uncaught TypeError: Cannot read property 'height' of undefined
Upon breaking and inspecting I can see the result of
$(this).find(".suggestionCategoryValues")
returns very different things. On the page that functions correctly:
$(this).find(".suggestionCategoryValues")
evaluates to:
[
<div class="suggestionCategoryValues">…</div>
]
But on the page where it fails it evaluates to:
[
b.fn.b.init[1]
0: div.suggestionCategoryValues
context: div.suggestionCategoryValues
length: 1
__proto__: Object[0]
]
Why is this and what am I doing wrong?
EDIT:
First off - I don't want to know how to fix it - I've already got a workaround (do an inner foreach and total up the sizes of $(".suggestionCategoryValues").height().
But I'm curious as to why they are different.
The HTML is identical I believe but I will post the markup just in case I'm being an idiot.
This is the markup for the "broken" page:
<div id="searchSuggestionSelection" style="display: block; position: absolute; top: 3.9em; left: 324px; height: 343px;">
<div class="suggestionCategory">
<div class="suggestionCategoryName"><span>company</span></div>
<div class="suggestionCategoryValues">
<div data-actual-search="XXXX" class="suggestionValue first">XXXX</div>
<div data-actual-search="YYYY" class="suggestionValue">YYYY</div>
<div data-actual-search="ZZZZ" class="suggestionValue">ZZZZ</div>
</div>
</div>
<div class="suggestionCategory">
<div class="suggestionCategoryName"><span>contact</span></div>
<div class="suggestionCategoryValues">
<div data-actual-search="AAAA" class="suggestionValue first">AAAA</div>
<div data-actual-search="BBBB" class="suggestionValue">BBBB</div>
<div data-actual-search="CCCC" class="suggestionValue">CCCC</div>
<div data-actual-search="DDDD" class="suggestionValue">DDDD</div>
</div>
</div>
</div>
And this is the markup for the "working" page:
<div id="searchSuggestionSelection" style="display: block; position: absolute; top: 3.9em; left: 324px; height: 823px;">
<div class="suggestionCategory">
<div class="suggestionCategoryName" style="height: 316px;"><span>company</span></div>
<div class="suggestionCategoryValues">
<div data-actual-search="XXXX" class="suggestionValue first">XXXX</div>
<div data-actual-search="YYYY" class="suggestionValue">YYYY</div>
<div data-actual-search="ZZZZ" class="suggestionValue">ZZZZ</div>
</div>
</div>
<div class="suggestionCategory">
<div class="suggestionCategoryName" style="height: 512px;"><span>contact</span></div>
<div class="suggestionCategoryValues">
<div data-actual-search="AAAA" class="suggestionValue first">AAAA</div>
<div data-actual-search="BBBB" class="suggestionValue">BBBB</div>
<div data-actual-search="CCCC" class="suggestionValue">CCCC</div>
<div data-actual-search="DDDD" class="suggestionValue">DDDD</div>
</div>
</div>
</div>
As you can see the "working" page successfully gets heights applied.
I think it is because "this" is something else than you have expected, try storing it in a variable (called category) like this:
$(".suggestionCategory").each(function() {
var category = $(this);
var height = category.find(".suggestionCategoryValues").height();
category.find(".suggestionCategoryName").outerHeight(height);
});
When doing some testing I'm not sure why you would do
.find(".suggestionCategoryName").outerHeight($(this).find(".suggestionCategoryValues").height());
When I did my testing the parameter in the outerHeight is the same as if you set it to outerHeight(true);
$(".suggestionCategory").each(function() {
var category = $(this);
category.find(".suggestionCategoryName").outerHeight(true);
});
//other method
var suggestCat = $('.suggestionCategory'),i;
for(i=0;i<suggestCat.length;i++){
var indexCat = suggestCat[i];
$(indexCat).find('.suggestionCategoryName').outerHeight(true);
}
Also if you are trying to set the height of an element wouldn't it be easier to use the height function?
var suggestCat = $('.suggestionCategory'),i;
for(i=0;i<suggestCat.length;i++){
var indexCat = suggestCat[i];
$(indexCat).height(function(ele,index){
return this.find('.suggestionCategoryName').height();
});
}
As I am very unsure what the issue is perhaps or read your post wrong I could be providing it the wrong answers. You can also add an if statement to check if the element exist first.
try this
$(".suggestionCategory").each(function() {
var suggestionCategory = $(this);
suggestionCategory.find(".suggestionCategoryName").outerHeight(suggestionCategory.find(".suggestionCategoryValues").height());
});
Wow - that was a lot of effort for something so trivial.
On the "broken" page I was also including something called moorainbow. A colour picker that works with mooTools. The version I was including was about 7 years old, and for some reason it was interfering with jQuery in a hideously hard to detect way.
Possibly putting jQuery into no-conflict mode might have solved it, which is probably best practice anyway, but would have involved a fair amount of re-factoring of $'s into jQuery's. So instead I just removed the mooRainbow from the page and voila - problem solved, both pages act identically again.
Heres my Jquery
$(".sectiontitle").click(function (e) {
$(this).next('div').slideToggle("slow");
el = $(this).find(".toggler > a.toggle");
currBg = el.css('background-image');
if (currBg == "url(http://blah/resources/img/close.gif)") {
currBg = "url(http://blah/resources/img/open.gif)";
console.log('open gif');
}
else {
currBg = "url(http://blah/resources/img/close.gif);"
console.log('close gif');
}
console.log(currBg);
el.css('background-image', currBg);
return false;
});
Heres my HTML panel (of which there are many)
<div class="majorsection">
<div class="sectiontitle">
<h2>Restaurant Bookings</h2>
<div class="toggler">
<a title="click to hide" class="toggle" href="http://blah/index.php/console/index"><span>-</span></a>
</div>
<div class="clear"></div>
</div>
<div class="msectioninner">
<div class="minorsection">
<div class="sectionlist">
<div class="section"></div>
</div>
<div class="sectionoptions">
<div class="clear"></div>
</div>
</div>
</div>
</div>
The image switches on the first click and the panel slides all cool both ways but the image doesn't change back
Why not use two css classes instead.
It will make the code much cleaner and maintainable.
Failing that one thing to try is to change
.css('background-image', currBg)
to
.css('backgroundImage', currBg)
I remember there was an issue with this (but thought it had been fixed). If this does not work have you got a url showing the issue?
Have you tried console.log(currBg); right after you retrieve it? The url() property may be getting rewritten/resolved. Not sure - but a similar problem arises if you are testing the .attr('src') of an image - it might not be what you set it to anymore.
A suggestion though: Rather than hard coding the background-image values, consider doing something like:
$(document).ready(function(){
$('a.toggle').addClass('closed');
$(".sectiontitle").click(function(e){
$(this).next('div').slideToggle("slow");
el = $(this).find(".toggler > a.toggle");
// jQuery 1.3 has this:
// el.toggleClass('.closed');
// otherwise - use this:
if (el.is('.closed'))
{
el.removeClass('closed');
} else {
el.addClass('closed');
}
return false;
});
});
Then your a.toggle picks up the background-image property of the "open" and a.toggle.closed gets the "closed" image in your CSS files.