I'm trying to pin some divs in place and fade them in and out as a user scrolls down. My code looks like this so far:
$(window).on("load",function() {
var fadeDuration = 500;
function fade() {
// compute current window boundaries
var windowTop = $(window).scrollTop(),
windowBottom = windowTop + $(window).innerHeight(),
focusElt = null;
// find our focus element, the first visible .copy element,
// with a short-circuiting loop
$('.imgdiv').toArray().some(function(e, i) {
var objectTop = $(e).offset().top;
if ((objectTop >= windowTop) && (objectTop <= windowBottom)) {
focusElt = e;
return true;
}
console.log(focusElt);
});
// obscure all others
$('.focus').not(focusElt)
.removeClass('focus')
.fadeTo(fadeDuration, 0);
// focus on our focus element; if was the previous focus, nothing
// to do; but if it wasn't focus / wasn't showing before, make
// it visible and have class focus
$(focusElt).not('.focus')
.addClass('focus')
.fadeTo(fadeDuration, 1);
}
fade(); //Fade in completely visible elements during page-load
$(window).scroll(function() {fade();}); //Fade in elements during scroll
});
Here's the corresponding fiddle that almost does what I'm looking for, but instead of the green "Fade In" blocks moving upward and fading, I want them pined in place near the top of the window. As the "IMG DIVs" move past them they will fade and reappear with each new "IMG DIV". Here, I'm focusing on the particular green block and fading it when it becomes the focus element. Instead, what I need to do is, focus on the IMG DIV blocks, add a "pinned" class to the green blocks when they reach the top of the page, and fade the green blocks in and out.
Does anyone have any advice?
Part 2 of my question is how to do this with native JavaScript, and not rely on jQuery's dependency.
Ok, so lets split your first issue into two issues :)
First of all, you want to (in general) do something when some element becomes visible in the viewport and when it becomes invisible. So, basically, all you need is function like that:
watchElementIsInViewport(
$('.imgdiv'),
doSomethingWhenElementAppearedInViewport,
doSomethingWhenElementOutOfViewport
);
You know, that when element becomes visible, you want to show some other element. When element becomes invisible, you want to hide that related element. So now, define those two functions:
function doSomethingWhenElementAppearedInViewport(element) {
// retrieve text related with the element
var $copy = $(element).next('.copy');
// fade it in
$copy.fadeTo(500, 1);
}
function doSomethingWhenElementGotOutOfViewport(element) {
// retrieve text related with the element
var $copy = $(element).next('.copy');
// fade it out
$copy.fadeTo(500, 0);
}
What about watchElementIsInViewport? There is no magic inside, only logic you already created, but decoupled from showing of finding elements.
function watchElementIsInViewport($elements, elementAppearedInViewport, elementGotOutOfViewport) {
var currentlyVisible = [ ];
// retrieve positions once, assume it won't change during script is working
var positions = getVerticalBoundaries($elements);
function _scrollHandler() {
var viewportTop = window.scrollY;
var viewportBottom = viewportTop + window.innerHeight;
$elements.each(function(index, element) {
var elementPosition = positions[index];
/* if you wish to check if WHOLE element is in viewport
* var elementIsInViewport = (elementPosition.top >= viewportTop) &&
* (elementPosition.bottom <= viewportBottom);
*/
var elementIsInViewport = (elementPosition.top < viewportBottom) &&
(elementPosition.bottom > viewportTop);
var elementIndexInCurrentlyVisible = currentlyVisible.indexOf(element);
// if element is visible but was not visible before
if(elementIsInViewport && (elementIndexInCurrentlyVisible === -1)) {
elementAppearedInViewport(element);
currentlyVisible.push(element);
// if element is not visible but was visible before
} else if(!elementIsInViewport && (elementIndexInCurrentlyVisible !== -1)) {
elementGotOutOfViewport(element);
currentlyVisible.splice(elementIndexInCurrentlyVisible, 1);
}
});
}
// initial check & update
_scrollHandler();
// check & update on every scroll
$(window).on('scroll', _scrollHandler);
}
And that's all. Working example.
Related
I'm trying to make the elements on the page fade in on scroll. Easy enough right? Not for me.
HTML is a standard list.
CSS sets all elements to opacity 0 prior to scrolling.
I'm trying to use Native JavaScript only.
// get current page body
var actBody = document.getElementById('acts-body');
// on scroll function
actBody.onscroll = function(){
// get screen height
var screenPosition = window.innerHeight;
// get all text elements
var artistName = document.getElementsByClassName('artist');
// loop through all elements
for(var i = 0; i < artistName.length; i++){
// get each elements position from top
var positionFromTop = artistName[i].getBoundingClientRect().top;
// if element is in viewport add class
if(positionFromTop - screenPosition <= 0){
artistName[i].classList.add('txt-fadeIn');
}
else{
artistName[i].classList.remove('txt-fadeIn');
}
console.log(artistName[i]);
}
i think it should solve it
if(screenPosition - positionFromTop <= 0){
artistName[i].classList.add('txt-fadeIn');
}
I made a function that Is made to be trigered when user scrolls on a element on the page. In this case when user scrolls to an id then it fades in. The problem is that they fade in all at the same time with the first scroll instead of when they reaching the element That is supposed to allow it to fade in! Please help me make my function work.
Thanks a lot
var selected={
//// Storing selectors
items:[],
/// Function that stores items and hides them from the page
selectFunc: function(select) {
//// Store selected element
selected.items.push(select);
/// hide selector from the page
$(select).hide();
}
};
//// Function triggeres on scroll
$(window).scroll(function() {
/// loops trough the selected elements
for(i=0; i<selected.items.length; i++){
var currentItem = selected.items[i];
///// calculates your position and item position
var hT = $(currentItem).offset().top,
hH = $(currentItem).outerHeight(),
wH = $(window).height(),
wS = $(this).scrollTop();
////// check if you are in the position
if (wS > (hT+hH-wH)){
$( currentItem ).fadeIn( 2500 );
}
}
});
//// Using my function to select id about and p element in it.
selected.selectFunc("#about p");
selected.selectFunc("#about input");
In your for loop, you are doing an iteration for each element in selected.items. What's in there? Two strings: "#about p", and "#about input".
So for each of these selectors, you show them all. You need to get every element separately.
Another problem is that hiding these elements means they are not taking up the space they should on the page, so you might not be able to scroll down. You can solve that by changing their opacity instead of making them display:none (what .hide() is doing).
Here is your code with some modifications:
var selected = {
//// Storing selectors
items: [],
/// Function that stores items and hides them from the page
selectFunc: function(select) {
//// Store selected element
var items = $(select);
for (var i = 0, l = items.length; i < l; i++) selected.items.push(items[i]);
/// hide selector from the page
items.css('opacity', 0);
}
};
//// Function triggeres on scroll
$(window).scroll(function() {
/// loops trough the selected elements
for (i = 0; i < selected.items.length; i++) {
var currentItem = selected.items[i];
///// calculates your position and item position
var hT = $(currentItem).offset().top,
hH = $(currentItem).outerHeight(),
wH = $(window).height(),
wS = $(this).scrollTop();
////// check if you are in the position
if (wS > (hT + hH - wH)) {
$(currentItem).animate({
'opacity': 1
}, 2500);
}
}
});
//// Using my function to select id about and p element in it.
selected.selectFunc("#about p");
selected.selectFunc("#about input");
// Simulating a scroll to show the first elements
$(window).scroll();
JS Fiddle Demo
The use case is that I have an html canvas on top of several html elements which listen for right click mouse events. I want to draw on the canvas using left mouse button, and at the same time interact with underlying html elements using right click.
I get that I can allow all mouse events to pass through the canvas by setting css property pointer-events to none. However I want to allow only right click to pass through it.
One way to achieve this may be to listen on the canvas for right click, set pointer-events to none in the callback, manually fire a right click event again and set pointer-events back to auto.
Btw I'm using KineticsJS and I have no idea how to manually fire mouse events using it.
Any suggestions would be appreciated.
Interesting subject that caught my attention. It was fun to come up with a solution to it based on this jQuery approach and some others. I'm not familiar with KineticsJS, but this is a plain javascript approach
In essence you can fake a pointer-events:none for just the right click by using the object's dimensions/positioning and onmousedown's event.which to determine if a right click was clicked on the background elements. The following is an example of that approach, hopefully the comments explain it well
// Get all overlaying canvases
var canvas = document.getElementsByTagName("canvas"),
// Get all elements that you want the click to fire on
background = document.getElementsByClassName("background");
// Use click location and dimensions/positioning to fake a click through
function passThrough(e) {
// Allow only right click to pass through
if(e.which == 2 || e.which == 3) {
// Check all background elements
for(var i = 0; i < background.length; i++) {
// check if clicked point (taken from event) is inside element
var mouseX = e.pageX;
var mouseY = e.pageY;
var obj = background[i];
var width = obj.clientWidth;
var height = obj.clientHeight;
if (mouseX > obj.offsetLeft && mouseX < obj.offsetLeft + width
&& mouseY > obj.offsetTop && mouseY < obj.offsetTop + height) {
// Force click event if within dimensions
background[i].onclick();
}
}
}
}
for(var i = 0; i < canvas.length; i++) {
// Force our function when clicked
canvas[i].onmousedown = passThrough;
// Prevent menu from appearing
canvas[i].oncontextmenu = function(event) { event.returnDefault; return false; }
}
for(var i = 0; i < background.length; i++) {
// Toggle background when clicked (to show it works)
background[i].onclick = function() {
if(this.style.background == "black") {
this.style.background = "red";
}
else {
this.style.background = "black";
}
}
}
I hope it suits your needs!
I have a HTML5 canvas, which is displaying a number of images and a paragraph of text on the page underneath the canvas. I want the text in the paragraph to be updated to display a different element from a JS array depending on which image the user clicks on.
Currently, I have a 'mousedown' function that looks like this:
_mousedown: function(evt) {
this._setUserPosition(evt);
var obj = this.getIntersection(this.getUserPosition());
if(obj && obj.shape) {
var shape = obj.shape;
this.clickStart = true;
shape._handleEvent('mousedown', evt);
isClickOnImage(evt);
var id = shape.id;
selectTip(id);
}
//init stage drag and drop
if(Kinetic.DD && this.attrs.draggable) {
this._initDrag();
}
}
I tried using the line var id = shape.id to update the ID that's being passed to the function, so that it will get the correct element from my 'tips' array, but for some reason, when I view the page in the browser, and click on an image, the text beneath the canvas is not updated. It seems that this function is not updating the 'id' variable to the ID of whichever image has been clicked.
After looking into this, it seems to me that I will want to use a loop inside the 'mousedown' function, that will take the 'id' of the image on which the click has been detected, and loop through my 'sources' array (which is where all of the images have been loaded from the HTML into the JS), checking at each position whether the image stored at that location has the same ID as that of the image that has been clicked on. If it does, the loop should set the text to the text stored at that position of the array, and if not, it should continue looking through the array until it find it. Would this make sense? I tried adding the following code to the 'mousedown' function, but it doesn't change the text as I expected:
var imageCheckArray = 0;
while(imageCheckArray < sources.length){
if(shape.id == sources[imageCheckArray]){
selectTip(imageCheckArray);
} else {
imageCheckArray++;
}
}
Is there something I'm missing from the loop?
The code for the whole function currently looks like this:
_mousedown: function(evt) {
this._setUserPosition(evt);
var obj = this.getIntersection(this.getUserPosition());
if(obj && obj.shape) {
var shape = obj.shape;
this.clickStart = true;
shape._handleEvent('mousedown', evt);
isClickOnImage(evt);
/*This line needs to get the element of the sources array that has been selected,
and then select the element at the same position from the tips array.*/
//var id = null;
var imageCheckArray = 0;
while(imageCheckArray < sources.length){
if(shape.id == sources[imageCheckArray]){
selectTip(imageCheckArray);
} else {
imageCheckArray++;
}
}
//var id =
//selectTip(id);
}
//init stage drag and drop
if(Kinetic.DD && this.attrs.draggable) {
this._initDrag();
}
}
Edit 11/01/2013 # 16:10
The code for selectTip is:
function selectTip(id){
$("#tipsParagraph").text(tips[id]);
}
and I've put a jsFiddle up here: http://jsfiddle.net/cd8G7/ although the 'result' panel is not showing what I actually see when I view the page in my browser- I get the canvas with all of the images displayed, and the paragraph underneath the canvas shows the text from the first element of my 'tips' array.
Edit 23/01/2013 # 13:50
Here's my isClickOnImage function:
function isClickOnImage(event){
var clickX = event.clientX;
var clickY = event.clientY;
//var imageCheckIteration = 0;
while(imageCheckIteration < sources.length){
if((clickX > sources[imageCheckIteration].x && clickX < sources[imageCheckIteration].x + imageWidth) &&
(clickY > sources[imageCheckIteration].y && clickY < sources[imageCheckIteration].y + imageHeight)){
/*This is where I need to print the variable that holds the text I want to display, but I need to display its contents
outside the canvas, in the <p></p> tags below. */
console.log("Click on image detected");
document.getElementById("tipsParagraph").innerHTML = sources[imageCheckIteration].data-tip /*tips[imageCheckIteration]*/;
} else {
document.getElementById("tipsParagraph").innerHTML = "";
}
}
}
What I intended that this function do is, capture the X & Y coordinates of any click on the canvas, and store them in the variables "clickX" and "clickY". Then, I have a variable called "imageCheckIteration" that has been initialised to 0, and while this variable is less than the length of my "sources" array (which is the array where all of the images have been stored), the function should check whether the click was on an area of the canvas that is covered by one of the images in the array.
If it was, then a console log should display the message "click on image detected", and the line
document.getElementById("tipsParagraph").innerHTML = sources[imageCheckIteration].data-tip;
should set the value of the "tipsParagraph" to be the value of the 'data-tip' attribute of whichever image is at the 'imageCheckIteration' position of the 'sources' array. If the click was detected on an area of the canvas that does not have an image displayed, then the value of the "tipsParagraph" should be set to hold nothing.
However, for some reason, when I view the page in the browser, the 'tipsParagraph' displays the text "This is where the text will be displayed", which is its default value, so that's fine. But, when I click on an image, or click anywhere else on the canvas, the text displayed in the 'tipsParagraph' is not updated.
I can't figure out why this is- can someone point me in the right direction? Does it mean that my isClickOnImage(event) function is never being called?
I simplified the way you are getting a reference to an image through the canvas. The trick here is to swap the z-index of the canvas and the image container and grab the reference to the image on the mouse up event. I don't know of a clean way to get elements behind a canvas, hence the workaround.
$('canvas').bind('mousedown', function(e) {
$('section').css('z-index', 4);
});
$('img').bind('mouseup', function(e) {
$('#tipsParagraph').text($(this).attr('id') + ":" + $(this).attr('alt'));
$('section').css('z-index', 2);
});
The second portion here is grabbing some attributes from the image itself and updating the text inside your div.
You can see more of the solution here.
I have multiple JavaScript draggable DIV windows. When clicking a DIV, I want the window to get the highest z-index value. I've made a solution by adding/removing classes to the element in focus, BUT, I would like the windows to keep their "layer" -order (as if the entire DIV window node was re-appended to the DOM when being clicked).
Let's say there are five DIV's in the DOM. div1, div2, div3, div4 and div5. -div5 is closest to the front and div1 is in the back and so on.
When clicking div1, -div1 will get focus and put to front, setting div5 back one step. Then clicking div3, -div3 gets closest to front and div1 and div5 are put back one step like this: div2, div4, div5, div1, div3.
If you don't want to loop through all your divs and don't want to mess up with z-index you can just append again that div to the parent element (the body?) before dragging.
function stepUpNode(elementDragged){
var parentNode = elementDragged.parentNode;
parentNode.appendChild(elementDragged);
}
If you'd like to do this without re-appending the element, my solution when I wrote something similar a while back was to keep track of the maximum z-index. Every time a window is brought forward, the maximum z-index is incremented and the element's z-index is set to the new value. Of course, if someone messes with the windows enough, they might end up having very large z-index values, so this isn't always the best solution.
var maximumZIndex = 1;
var bringForward = function (element) {
maximumZIndex += 1;
element.style.zIndex = maximumZIndex;
}
The first and most likely easiest approach: simply increase the maximum z-index every time a div gets selected. Since the z-index value can become pretty large (2147483647 if I remember correctly) you most likely will never run out of levels...
The following snippets use some jQuery:
var frontmostWindow = null;
var topZIndex = 10;
$('div').click(function() {
if (this != frontmostWindow) {
frontmostWindow = this;
topZIndex++;
$(this).css('zLevel', topZIndex);
// anything else needed to acticate your div
// ...
}
});
If you have restrictions on the z-indices you can use, you will need to re-assign levels every time a different div gets selected, e.g. like this:
// store z-index-ordered divs in an array
var divs = $('div').toArray().sort(function(a, b) {
return parseInt($(a).css('zIndex'), 10) - parseInt($(b).css('zIndex'), 10);
});
// store available z-indices
var zIndices = [];
for (var i = 0; i < divs.length; ++i) {
zIndices.push($(divs[i]).css('zIndex'));
}
// Event listener for clicks
$('div').click(function() {
alert("heyya " + this.id);
var element = this;
var index = divs.indexOf(element);
// check if clicked element is not already the frontmost
if (index < divs.length - 1) {
// remove div from array and insert again at end
divs.splice(index, 1);
divs.push(this);
// re-assign stored z-indices for new div order
for (var i = 0; i < divs.length; ++i) {
$(divs[i]).css('zIndex', zIndices[i]);
}
// anything else needed to acticate your div
// ...
}
});