I am creating an image hover effect but I am having problem with it. When I hover over certain images, the scrollbars appear which I want to avoid but don't know how to do so. I believe it has to do with viewport and calculations but am not sure how that is done.
Example Here
JSBin Code
Here is the code:
$('.simplehover').each(function(){
var $this = $(this);
var isrc = $this[0].src, dv = null;
$this.mouseenter(function(e){
dv = $('<div />')
.attr('class', '__shidivbox__')
.css({
display: 'none',
zIndex : 9999,
position: 'absolute',
top: e.pageY + 20,
left: e.pageX + 20
})
.html('<img alt="" src="' + isrc + '" />')
.appendTo(document.body);
dv.fadeIn('fast');
})
.mouseleave(function(){
dv.fadeOut('fast');
});
});
Can anyone please help me how do I make it so that hovered image appears at such place that scrollbars dont appear? (Of course we can't avoid scrollbar if image size is very very big)
I just want to show original image on zoom while avoiding scrollbars as much as possible.
Please note that I am planning to convert it into jQuery plugin and therefore I can't force users of plugin to have overflow set to hidden. The solution has do with viewport, left, top, scroll width and height, window width/height properties that I can incorporate into plugin later on.
Update:
I have come up with this:
http://jsbin.com/upuref/14
However, it is very very hacky and not 100% perfect. I am looking for a better algorithim/solution. I have seen many hover plugins that do this very nicely but since I am not that good at this, I can't do it perfectly well. For example Hover Zoom Chrome Plugin does great job of showing hovered images at appropriate place based on their size.
Like this:
html{overflow-x:hidden;}
html{overflow-y:hidden;}
All you need to do is add these definitions to your CSS and you're done.
Update with Resize: this is the mouseenter code for resizing and repositioning the pictures BOTH horizontally and vertically. Now, no matter where the HOVER image shows up, it's resized and positioned to always show in full AND uncut. As far as the scrollbars are concerned, if you show more thumbnails than can fit on the page, you will have scrollbars even before the HOVER images show up.
FINAL AND WORKING UPDATE: Because you had focused on the scrollbars being hidden, I think you overlooked the fact that if you put more thumbnails than the viewport can contain, the scrollbars would show up anyway and that therefore, since the user can scroll down the document, when you calculate the position of the hover image, not only do you need to account for the resize but you also to account for the scrollTop position too! FINAL JSBIN HERE, all pictures are showing RESIZED and in FULL no matter where the scrollTop is and no matter what the viewport size is.
$this.mouseenter(function () {
dv = $('<div />')
.attr('class', '__shidivbox__')
.css({
'display': 'none',
'z-index': 9999,
'position': 'absolute',
'box-shadow': '0 0 1em #000',
'border-radius': '5px'
})
.html('<img alt="" src="' + isrc + '" />')
.appendTo(document.body);
var DocuWidth = window.innerWidth;
var DocuHeight = window.innerHeight;
var DvImg = dv.find('img');
var TheImage = new Image();
TheImage.src = DvImg.attr("src");
var DivWidth = TheImage.width;
var DivHeight = TheImage.height;
if (DivWidth > DocuWidth) {
var WidthFactor = (DivWidth / DocuWidth) + 0.05;
DivHeight = parseInt((DivHeight / WidthFactor), 10);
DivWidth = parseInt((DivWidth / WidthFactor), 10);
}
var ThumbHeight = $this.height();
var ThumbWidth = $this.width();
var ThumbTop = $this.position().top;
var ThumbLeft = $this.position().left;
var SpaceAboveThumb = ThumbTop - $(document).scrollTop();
var SpaceBelowThumb = DocuHeight - ThumbTop - ThumbHeight + $(document).scrollTop();
var MaxHeight = Math.max(SpaceAboveThumb, SpaceBelowThumb);
if (DivHeight > MaxHeight) {
var HeightFactor = (DivHeight / MaxHeight) + 0.05;
DivHeight = parseInt((DivHeight / HeightFactor), 10);
DivWidth = parseInt((DivWidth / HeightFactor), 10);
}
var HoverImgLeft = 0;
var HoverImgTop = 0;
if (SpaceBelowThumb > SpaceAboveThumb) {
HoverImgTop = ThumbTop + ThumbHeight;
} else {
HoverImgTop = ThumbTop - DivHeight;
}
HoverImgTop = (HoverImgTop < 0) ? 0 : HoverImgTop;
HoverImgLeft = (DocuWidth - DivWidth) / 2;
dv.find('img').css({
'width': DivWidth,
'height': DivHeight,
'border-radius': '5px'
});
dv.css({
'left': HoverImgLeft,
'top': HoverImgTop
});
dv.fadeIn('fast');
});
Well, this looks fun. Anyway, here's my answer. I've been watching this for a few days and though I'd chip in too. The following will make sure that the hovering images do not go out of the viewport and in the event that the width of the image is bigger than the available space for display, the display of the image will be resized (You can comment out the code that does this if you don't want it. Just look for the word "resize" in the code).
var $document = $(document);
$('.simplehover').each(function(){
var $this = $(this);
// make sure that element is really an image
if (! $this.is('img')) return false;
var isrc = $this[0].src, ibox = null;
if (! isrc) return false;
ibox = $('<img />')
.attr('class', 'simpleimagehover__shidivbox__')
.css({
display: 'none',
zIndex : 99,
MozBoxShadow: '0 0 1em #000',
WebkitBoxShadow: '0 0 1em #000',
boxShadow: '0 0 1em #000',
position: 'absolute',
MozBorderRadius : '10px',
WebkitBorderRadius : '10px',
borderRadius : '10px'
})
.attr('src', isrc)
.appendTo(document.body);
$this.bind('mouseenter mousemove', function(e) {
$('.simpleimagehover__shidivbox__').hide();
var left = e.pageX + 5,
top = e.pageY + 5,
ww = window.innerWidth,
wh = window.innerHeight,
w = ibox.width(),
h = ibox.height(),
overflowedW = 0,
overflowedH = 0;
// calucation to show element avoiding scrollbars as much as possible - not a great method though
if ((left + w + $document.scrollLeft()) > ww)
{
overflowedW = ww - (left + w + $document.scrollLeft());
if (overflowedW < 0)
{
left -= Math.abs(overflowedW);
}
}
// 25 is just a constant I picked arbitrarily to compensate pre-existing scrollbar if the page itself is too long
left -= 25;
left = left < $document.scrollLeft() ? $document.scrollLeft() : left;
// if it's still overflowing because of the size, resize it
if (left + w > ww)
{
overflowedW = left + w - ww;
ibox.width(w - overflowedW - 25);
}
if (top + h > wh + $document.scrollTop())
{
overflowedH = top + h - wh - $document.scrollTop();
if (overflowedH > 0)
{
top -= overflowedH;
}
}
top = top < $document.scrollTop() ? $document.scrollTop() : top;
ibox.css({
top: top,
left: left
});
ibox.show();
});
$('.simpleimagehover__shidivbox__').mouseleave(function(){
$('.simpleimagehover__shidivbox__').hide();
});
$document.click(function(e){
$('.simpleimagehover__shidivbox__').hide();
});
$document.mousemove(function(e){
if (e.target.nodeName.toLowerCase() === 'img') {
return false;
}
$('.simpleimagehover__shidivbox__').hide();
});
});
While my solution itself is not perfect, you might find something useful in there that can help you determine the viewport. Also, you might want to think about the performance of the code. Since this is a plugin that you're building, you'll want to make some optimizations before releasing it to public. Basically, just make sure it's not slow.
You can position the image based on the available width: http://jsbin.com/upuref/19/
This demo takes in account the available space for positioning the image (i.e. the window width minus the image width). Also I've improved the event order, with the popup div only starting its fade-in after the image has been loaded.
My answer too (JSBin DEMO)
$('.simplehover').each(function(){
var $this = $(this);
// make sure that element is really an image
if (! $this.is('img')) return false;
var isrc = $this[0].src, dv = null;
if (! isrc) return false;
$this.mouseenter(function(e){
// mouse x position
var initXPos = e.pageX;
var initYPos = e.pageY+20-$(window).scrollTop();
var windowWidth = $(window).width();
var windowHeight = $(window).height();
// load original image
var $img = $('<img/>');
$img.on('load',function(eload) {
var widthImage = this.width;
var heightImage = this.height;
// set inline style for get sizes after (see problems webkit and cache)
$(this).css('width',widthImage);
$(this).css('height',heightImage);
var ratio = widthImage/heightImage;
var finalXPos = initXPos+widthImage>windowWidth? windowWidth-widthImage-5 : initXPos;
var finalYPos = initYPos;
var img = this;
// resize image if is bigger than window
if(finalXPos<0) {
finalXPos = 0;
$img.css('width', windowWidth-10);
$img.css('height',(windowWidth-10)/ratio);
}
// If overflow Y
if(finalYPos+getSize($img,'height')>windowHeight) {
// calculate where is more space (top or bottom?)
var showOnTop = (windowHeight-initYPos-10)<windowHeight/2;
if(showOnTop) {
if(initYPos<getSize($img,'height')) {
$img.height(initYPos-30);
$img.width(getSize($img,'height')*ratio);
}
finalYPos = 0;
finalXPos = initXPos+getSize($img,'width')>windowWidth? windowWidth-getSize($img,'width')-5 : initXPos;
}else {
// show on bottom
if(windowHeight-initYPos<getSize($img,'height')) {
$img.height(windowHeight-initYPos-10);
$img.width(getSize($img,'height')*ratio);
}
finalXPos = initXPos+getSize($img,'width')>windowWidth? windowWidth-getSize($img,'width')-5 : initXPos;
}
}
dv = $('<div />')
.attr('class', '__shidivbox__')
.css({
display: 'none',
zIndex : 9999,
position: 'absolute',
MozBorderRadius : '5px',
WebkitBorderRadius : '5px',
borderRadius : '5px',
top: finalYPos+$(window).scrollTop(),
left: finalXPos
}).append($img)
.appendTo(document.body);
dv.fadeIn('fast');
});
// load the original image (now is the same, but I think is better optimize it)
$img.attr("src",$this.attr("src"));
function getSize($el,widthOrHeight) {
// horrible but working trick :)
return +$el.css(widthOrHeight).replace("px","");
}
})
.mouseleave(function(){
dv.fadeOut('fast');
});
});
this script adapt the image to window size and adjust x position if needed.
Related
I've tried to find similar posts about this but failed to do so. What I'm trying to do is set up a parallax background that has a moderate zoom upon scrolling. I have the parallax down, that was simple enough, but the zoom on scroll is causing me difficulties.
if ($(".zoomImage").length == 0)
{
console.warn("You're attempting to set hero images without an existing class. '.heroImage'");
return;
}
$(document).scroll(function(){
var scrollpos = $(this).scrollTop();
var screenHeight = $(window).height();
var screenWidth = $(window).width();
$(".zoomImage").each(function(){
var offset = $(this).offset().top;
// Only modify when top is at top of browser on screen.
if (offset < scrollpos && scrollpos < offset + screenHeight)
{
var heroEffectPerc = 100 + 25 * (scrollpos - offset) / (screenHeight * 1.0);
$(this).css("background-size", heroEffectPerc + "% auto");
}
});
});
This is where I'm doing the zoom for the image, the parallax is done in pure CSS as represented below. The issue I'm having is figuring out the mathematics to make sure that the image doesn't escape the edge of its parent when the screen gets excessively wide or tall and still achieve the same effect. 1:
CSS:
pageCanvas
{
position: relative;
background-repeat: no-repeat;
background-position: center;
background-size: auto 100%;
background-color: white;
display: block;
left: 0;
top: 0;
width: 100%;
height: 100vh;
}
pageCanvas.parallax
{
background-attachment: fixed;
}
.
<pageCanvas class="parallax zoomImage" style="background-image: url('./Images/DisplayBackground.png');">
<banner>
<header>
Company name
</header>
<description>
I don't want to<br><span style="margin-left: 200px;">advertise here.</span>
</description>
</banner>
</pageCanvas>
I've tried to get it working, but either have an issue with one of the following:
White background shows on too wide.
White background shows on too tall.
Image stretching.
Do I need to bring in the images origin ratio with this or something? If I figure out a solution prior to an answer given, I'll post it.
Although the original was in a namespace, I'm going to place this answer as if it were not because I hadn't specified. Either way, I found the solution.
The first step was finding the ratio of the original image;
$(".zoomImage").each(function(index){
var bg = $(this).css('background-image');
bg = bg.replace('url(','').replace(')','').replace(/\"/gi, "");
// get image size.
var tmpImg = new Image();
tmpImg.src=bg;
$(tmpImg).one('load', function(){
orgWidth = tmpImg.width;
orgHeight = tmpImg.height;
bgImageRatios[index] = orgHeight / (orgWidth * 1.0);
});
});
To make life easier, I placed them in an array that was global to the name space. This is so I don't have to A) keep finding the ratio of the image, and B) can access it similarly to initializing later on. It should be noted that this method would require being called again in the instance there is any more or less '.zoomImage' classes brought into instance, as the array will be incorrect at that point.
What I did next was place the original code that loops the class into a function.
function zoomImage(scrollpos, screenHeight, screenWidth)
{
//console.log(screenHeight);
$(".zoomImage").each(function(index){
var offset = $(this).offset().top;
if (offset < scrollpos && scrollpos < offset + screenHeight)
{
var heroEffectPerc = 100 + 25 * (scrollpos - offset) / (screenHeight * 1.0);
if ((bgImageRatios[index] * screenWidth / screenHeight) > 1)
$(this).css("background-size", heroEffectPerc + "% auto");
else
$(this).css("background-size", "auto " + heroEffectPerc + "%");
}
});
}
I put it into a function because it would have been placed into two separate locations otherwise. (that's just messy coding). I updated the image size as follows.
$(window).on("resize", function(){
var scrollpos = $(document).scrollTop();
var screenHeight = $(this).height();
var screenWidth = $(this).width();
pageCanvas.zoomImage(scrollpos, screenHeight, screenWidth);
});
$(document).on("scroll", function(){
var scrollpos = $(this).scrollTop();
var screenHeight = $(window).height();
var screenWidth = $(window).width();
pageCanvas.zoomImage(scrollpos, screenHeight, screenWidth);
});
The following sources helped me solve my answer:
Can I get div's background-image url?
How to get image size from URL
Credit is due to them.
if i zoom the image it will going to the right. here is my js function below. the moment i zoom the image the background actual width and height is constant only the background size should be dynamic. that's the reason why it looks like if it zoom it will going to the right not in the center. what i want is when i zoom the image it zoom it forward to the center of the image.
function zooms(zoomType) {
switch(zoomType) {
case '+':
newW += 10;
break;
case '-':
newW -= 10;
break
}
if(newW <= 0) {
newW = 0;
} else {
if(newW >= 100) {
newW = 100;
}
}
return newW;
}
function backgroundAdjust(num, el) {
wid = (parseInt(num)+parseInt(el.width()));
el.css({
'background-size': wid + 'px auto'
});
return;
}
$('input.zoomButton').click(function() {
zoomType = $(this).val();
zoomNum = zooms(zoomType);
backgroundAdjust(zoomNum, $('#uploadImage'));
});
This should work if your image is square : it offsets the background image so its center remains at the center of the containing element.
var posx = (el.width()-wid)/2;
var posy = (el.height()-wid)/2;
el.css({
'background-size': wid + 'px auto',
'background-position': posx +'px ' +posy +'px'
});
What worked for me was scrolling the container element by half the value of same amount you are increasing the width and height of the image. For this you can use scrollBy. Eg. if your container element is el and the image inside is img. Now to zoom-in if you are increasing the width and height by 10% of the current 'x' px and height by 'y' px, Your zoom-in function would look like below:
function zoomIn() {
let imgWidth = img.width.replace(/px/g,""); // removing 'px'
let imgHeight = img.height.replace(/px/g,""); // removing 'px'
let x = imgWidth + (imgWidth*0.1);
let y = imgHeight + (imgHeight*0.1);
el.scrollBy(x/2, y/2);
}
I need to retrieve the visible height of a div within a scrollable area. I consider myself pretty decent with jQuery, but this is completely throwing me off.
Let's say I've got a red div within a black wrapper:
In the graphic above, the jQuery function would return 248, the visible portion of the div.
Once the user scrolls past the top of the div, as in the above graphic, it would report 296.
Now, once the user has scrolled past the div, it would again report 248.
Obviously my numbers aren't going to be as consistent and clear as they are in this demo, or I'd just hard code for those numbers.
I have a bit of a theory:
Get the height of the window
Get the height of the div
Get the initial offset of the div from the top of the window
Get the offset as the user scrolls.
If the offset is positive, it means the top of the div is still visible.
if it's negative, the top of the div has been eclipsed by the window. At this point, the div could either be taking up the whole height of the window, or the bottom of the div could be showing
If the bottom of the div is showing, figure out the gap between it and the bottom of the window.
It seems pretty simple, but I just can't wrap my head around it. I'll take another crack tomorrow morning; I just figured some of you geniuses might be able to help.
Thanks!
UPDATE: I figured this out on my own, but looks like one of the answers below is more elegant, so I'll be using that instead. For the curious, here's what I came up with:
$(document).ready(function() {
var windowHeight = $(window).height();
var overviewHeight = $("#overview").height();
var overviewStaticTop = $("#overview").offset().top;
var overviewScrollTop = overviewStaticTop - $(window).scrollTop();
var overviewStaticBottom = overviewStaticTop + $("#overview").height();
var overviewScrollBottom = windowHeight - (overviewStaticBottom - $(window).scrollTop());
var visibleArea;
if ((overviewHeight + overviewScrollTop) < windowHeight) {
// alert("bottom is showing!");
visibleArea = windowHeight - overviewScrollBottom;
// alert(visibleArea);
} else {
if (overviewScrollTop < 0) {
// alert("is full height");
visibleArea = windowHeight;
// alert(visibleArea);
} else {
// alert("top is showing");
visibleArea = windowHeight - overviewScrollTop;
// alert(visibleArea);
}
}
});
Calculate the amount of px an element (height) is in viewport
Fiddle demo
This tiny function will return the amount of px an element is visible in the (vertical) Viewport:
function inViewport($el) {
var elH = $el.outerHeight(),
H = $(window).height(),
r = $el[0].getBoundingClientRect(), t=r.top, b=r.bottom;
return Math.max(0, t>0? Math.min(elH, H-t) : Math.min(b, H));
}
Use like:
$(window).on("scroll resize", function(){
console.log( inViewport($('#elementID')) ); // n px in viewport
});
that's it.
jQuery .inViewport() Plugin
jsFiddle demo
from the above you can extract the logic and create a plugin like this one:
/**
* inViewport jQuery plugin by Roko C.B.
* http://stackoverflow.com/a/26831113/383904
* Returns a callback function with an argument holding
* the current amount of px an element is visible in viewport
* (The min returned value is 0 (element outside of viewport)
*/
;(function($, win) {
$.fn.inViewport = function(cb) {
return this.each(function(i,el) {
function visPx(){
var elH = $(el).outerHeight(),
H = $(win).height(),
r = el.getBoundingClientRect(), t=r.top, b=r.bottom;
return cb.call(el, Math.max(0, t>0? Math.min(elH, H-t) : Math.min(b, H)));
}
visPx();
$(win).on("resize scroll", visPx);
});
};
}(jQuery, window));
Use like:
$("selector").inViewport(function(px) {
console.log( px ); // `px` represents the amount of visible height
if(px > 0) {
// do this if element enters the viewport // px > 0
}else{
// do that if element exits the viewport // px = 0
}
}); // Here you can chain other jQuery methods to your selector
your selectors will dynamically listen to window scroll and resize but also return the initial value on DOM ready trough the first callback function argument px.
Here is a quick and dirty concept. It basically compares the offset().top of the element to the top of the window, and the offset().top + height() to the bottom of the window:
function getVisible() {
var $el = $('#foo'),
scrollTop = $(this).scrollTop(),
scrollBot = scrollTop + $(this).height(),
elTop = $el.offset().top,
elBottom = elTop + $el.outerHeight(),
visibleTop = elTop < scrollTop ? scrollTop : elTop,
visibleBottom = elBottom > scrollBot ? scrollBot : elBottom;
$('#notification').text(`Visible height of div: ${visibleBottom - visibleTop}px`);
}
$(window).on('scroll resize', getVisible).trigger('scroll');
html,
body {
margin: 100px 0;
}
#foo {
height: 1000px;
background-color: #C00;
width: 200px;
margin: 0 auto;
}
#notification {
position: fixed;
top: 0;
left: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<div id="foo"></div>
<div id="notification"></div>
The logic can be made more succinct if necessary, I've just declared separate variables for this example to make the calculation as clear as I can.
Here is a version of Rory's approach above, except written to function as a jQuery plugin. It may have more general applicability in that format. Great answer, Rory - thanks!
$.fn.visibleHeight = function() {
var elBottom, elTop, scrollBot, scrollTop, visibleBottom, visibleTop;
scrollTop = $(window).scrollTop();
scrollBot = scrollTop + $(window).height();
elTop = this.offset().top;
elBottom = elTop + this.outerHeight();
visibleTop = elTop < scrollTop ? scrollTop : elTop;
visibleBottom = elBottom > scrollBot ? scrollBot : elBottom;
return visibleBottom - visibleTop
}
Can be called with the following:
$("#myDiv").visibleHeight();
jsFiddle
Here is the improved code for jquery function visibleHeight: $("#myDiv").visibleHeight();
$.fn.visibleHeight = function() {
var elBottom, elTop, scrollBot, scrollTop, visibleBottom, visibleTop, height;
scrollTop = $(window).scrollTop();
scrollBot = scrollTop + $(window).height();
elTop = this.offset().top;
elBottom = elTop + this.outerHeight();
visibleTop = elTop < scrollTop ? scrollTop : elTop;
visibleBottom = elBottom > scrollBot ? scrollBot : elBottom;
height = visibleBottom - visibleTop;
return height > 0 ? height : 0;
}
I have multiple sections showing different backgrounds, each section has a basic parallax background image. As the backgrounds vary in height, I cannot seem to work out how to stop the background image position once the image bottom is reached.
Background position change begins if the section offset().top is equal to or greater than $(window).scrollTop().
It would seem that the btmOffset is incorrect but I can't see why.
Any help greatly appreciated.
Live example
http://demo.dwweb.co.uk
What I have so far
$window = $(window);
var winWid = $window.width();
$('.portfolioSection').each(function(){
var $bgobj = $(this);
var speed = 2.4;
var bg = $(this).css('background-image').replace('url("','').replace('")','');
var tmpImg = new Image();
tmpImg.src = bg;
var orgW = tmpImg.width;
var orgH = tmpImg.height;
var imgResizedRatio = winWid/orgW;
var resizedH = orgH * imgResizedRatio;
var btmOffset = (resizedH - $(this).height()) + $bgobj.offset().top;
$(window).scroll(function() {
if($(window).scrollTop() > $bgobj.offset().top && $(window).scrollTop() < btmOffset){
var yPos = -(($window.scrollTop()-$bgobj.offset().top) / speed);
var coords = '0 '+ yPos + 'px';
$bgobj.css({ backgroundPosition: coords });
} else if($(window).scrollTop() < $bgobj.offset().top) {
$bgobj.css({ backgroundPosition: '0 0' });
} else {
$bgobj.css({ backgroundPosition: '0 '+resizedH+'px' });
}
});
});
orgH and orgW will be 0 on execution, since you are creating a new Image asynchronously, while executing your code synchronously:
var tmpImg = new Image();
tmpImg.src = bg;
//...
This means, you would have to use the onload event (and maybe cover the onerror event too), like this:
tmpImg.onload = function(ev) {
var orgW = tmpImg.width;
var orgH = tmpImg.height;
//and the rest of your code...
}
This is very inefficient, since you are loading all these (big) images again.
I would add data-attributes to each .portfolioSection, like data-bgmaxscroll="1000" (which is the height of the image).
This would be a bit more hardcoded, but i think it's the easiest and the most performant way.
I would like to create a div, that is situated beneath a block of content but that once the page has been scrolled enough to contact its top boundary, becomes fixed in place and scrolls with the page. I know I've seen at least one example of this online but I cannot remember it for the life of me.
Any thoughts?
[Working demo]
var el = $("#sticky");
var win = $(window);
var width = el.width();
var height = el.height();
var win_height = $(window).height();
window.onscroll = function() {
var offset = el.offset().top + height - win_height;
if ( win.scrollTop() > offset ) {
window.onscroll = function() {
el.css({
width: width,
position: "absolute",
top: win.scrollTop() + win_height - height
});
};
}
};
If you don't need to support IE based browsers you can use:
position: "fixed"
bottom: 0