Changing opacity breaks positioning of image - javascript

I'm trying to make a random slideshow of images for a gallery. I have two images on the page – the "front" one and the "back" one, and I have a timer set up to, every few seconds, move the back image to the front and load a new back image. I'm basically doing this by swapping the image objects in the code.
As the back image becomes the front image, I have it fade in gradually from an opacity of 0 to an opacity of 1, while I have the front image do the same in reverse. I implemented this as follows:
var fadeOutCt = 0;
var fadeOutInterval;
// Decrements the opacity of element by amt, until cap
function decrementOpacity( element, amt, cap ) {
var currentOpacity = Number( window.getComputedStyle( element ).getPropertyValue( "opacity" ) );
currentOpacity = currentOpacity - amt;
element.setAttribute( "style", "opacity:" + currentOpacity );
fadeOutCt = fadeOutCt + 1;
if ( fadeOutCt >= cap ) {
element.setAttribute( "style", "opacity:0" );
clearInterval( fadeOutInterval );
}
}
// Calls decrementOpacity to fill the specified interval.
function fadeOut( element, interval ) {
var currentOpacity = Number( window.getComputedStyle( element ).getPropertyValue( "opacity" ) );
fadeOutCt = 0;
if ( currentOpacity > 0 ) {
cap = interval / 10.0;
amt = 1.0 / cap;
fadeOutInterval = setInterval( decrementOpacity, 10, element, amt, cap );
}
}
Separately, I have another routine that resizes the image I load to conform to the user's screen (it also centers the image). I run this immediately before the fade-in or fade-out operation.
function resizeForSlideshow( imgToResize ) {
// Get size of usable area for slideshow
var usableWidth = window.innerWidth;
var titleTable = document.getElementById( "descTable" );
var windowHeight = window.innerHeight;
var tableHeight = titleTable.offsetHeight;
var usableHeight = windowHeight - tableHeight;
// Resize containing div
var slideDiv = document.getElementById( "slideDiv" );
slideDiv.setAttribute( "style", "height:" + usableHeight + "px" );
// Get size of native image to be displayed
var nativeWidth = imgToResize.naturalWidth;
var nativeHeight = imgToResize.naturalHeight;
// Determine width-to-height ratios of those two
var windowRatio = usableWidth / usableHeight;
var imageRatio = nativeWidth / nativeHeight;
if ( imageRatio > windowRatio ) {
// image's width-to-height is greater than window
// image should be set to 100% width, less height
imgToResize.width = usableWidth;
imgToResize.height = usableWidth * ( nativeHeight / nativeWidth );
// relocate image accordingly
var newTop = ( usableHeight - imgToResize.height ) / 2;
imgToResize.style.left = 0;
imgToResize.style.top = newTop + "px";
}
else {
// image's width-to-height is less than window
// image should be set to 100% height, less width
imgToResize.height = usableHeight;
imgToResize.width = usableHeight * ( nativeWidth / nativeHeight );
// relocate image accordingly
var newLeft = ( usableWidth - imgToResize.width ) / 2;
imgToResize.style.top = 0;
imgToResize.style.left = newLeft + "px";
}
}
The problem is, when I fade in or fade out, it breaks the positioning of my images. Instead of being centered, they revert to being on the top left of the page (though their size remains what it should be). I've tried a few things but I'm out of ideas, and I was hoping someone would be able to shed some light on what's going wrong here and how I could fix it.
(If seeing the code in action would help: http://artmonitors.com/slideshow/full-slideshow.html
EDIT: I later figured out the problem.
The problem is that using setAttribute to set opacity also removed the positional settings I had given. Manually setting element.style.opacity made things work.

How about centering it with css?
.slide {
display: block;
margin: auto;
position: relative;
}

You should close the style with ;
element.setAttribute( "style", "opacity:" + currentOpacity +";");

I later figured out the problem. The problem is that using setAttribute to set opacity also removed the positional settings I had given. Manually setting element.style.opacity made things work.

Related

getBoundingClientRect seems inaccurate when element is being transformed

I'm trying to use a CSS transform to translate an absolutely positioned SVG element diagonally down/right across the viewport on scroll, and I need the individual paths to change their fill colour as they cross over the next element, but it seems like getBoundingClientRect isn't returning the correct values during scroll.
Here's a demo: https://codepen.io/ahollister/pen/mdyvQLN
And here's the JS:
window.addEventListener('scroll', function(e) {
let scrollPercent = ( window.scrollY ) / ( document.body.clientHeight - window.innerHeight );
let scrollPercentRounded = Math.round( scrollPercent * 100 );
document.querySelector('.arrows-container').style.transform = `translate(${scrollPercentRounded}%, ${scrollPercentRounded}%)`
const arrowsArray = document.querySelectorAll('svg path');
const el = document.querySelector('.bottom');
for ( const a of arrowsArray ) {
if ( a.getBoundingClientRect().bottom > el.offsetTop ) {
a.style.fill = 'red';
}
}
});
I'm trying to get each arrow to change fill colour as they cross the line into the .bottom element, if you comment out the transform line it seems to calculate everything correctly:
document.querySelector('.arrows-container').style.transform = `translate(${scrollPercentRounded}%, ${scrollPercentRounded}%)`
Anyone come across this issue before? How can I get getBoundingClientRect to return the correct values in this instance?
Figured it out, in the end I wasn't taking the scrollY position into account. Ended up with a sort of rudimentary collision detection function which looks like this:
function colorOnCollision( svgPaths, el, color1, color2 ) {
for ( const path of svgPaths ) {
// If path is over el1 or el2
if ( path.getBoundingClientRect().bottom < ( el.offsetHeight - window.scrollY ) ) {
path.style.fill = color1;
} else {
path.style.fill = color2;
}
}
}

Variable keeps growing even if condition is not met

I'm trying to animate a div on scroll. The point is that the div's width must grow until it reaches 80vw and stop. This does happen, but my variable keeps on growing (it's being logged to the console) even if the >=outerWidth*0.8 condition isn't met. Thanks to this, whenever I get to 80vw and scroll up and then down, the width becomes Xvw.
$(window).on('scroll',function(){
var scrollTop = $(this).scrollTop();
var outerHeight = $(this).outerHeight();
var outerWidth = $(this).outerWidth();
var scrollBottom = scrollTop + outerHeight;
var scrollTop = $(this).scrollTop();
console.log( growNaranja );
if (scrollTop > lastScrollTop){ // scroll down
if( naranjaWidth <= (outerWidth*0.8) ){
growNaranja = (naranja.outerWidth()*100) / outerWidth;
growNaranja = growNaranja+(scrollTop*0.05);
$('.grow.naranja').css( 'width', growNaranja + 'vw' );
}
} else { // scroll up
if( naranjaWidth >= (outerWidth*0.1) ){
growNaranja = (naranja.outerWidth()*100) / outerWidth;
$('.grow.naranja').css( 'width', growNaranja + 'vw' );
growNaranja = growNaranja - ((lastScrollTop-scrollTop)*0.05);
$('.grow.naranja').css( 'width', growNaranja + 'vw' );
}
}
lastScrollTop = scrollTop;
});
You can see a working example here.
Revisited this one, it was bugging me. First, the code was all spaghetti. Second, there was really function duplication. You had a function for scrolling up and one for scrolling down, and you were using the last scrollTop to calculate the next scroll step. Instead, I've made a single scale function that gets called regardless. The value of the percentage scrolled is multiplied by the step factor, and that is added to the ORIGINAL element width. By doing this, I'm not worried about where I was just prior to the scroll, only where I am now.
So I made the scaleWidthEl an object constructor, and simply wrapped the naranja div in that. The actual code to create it is the first three lines, and could be reduced to:
var scaleNaranja = new ScaleWidthEl($('.grow.naranja'), 0.8);
The rest is self-contained, allowing changes to be made without affecting anything else.
var maxElScale = 0.8;
var naranja = $('.grow.naranja');
var scaleNaranja = new ScaleWidthEl(naranja, maxElScale);
/***
* The rest of this is a black-box function, walled away from the main code
* It's a personal peeve of mine that code gets garbled otherwise.
***/
function ScaleWidthEl(el, maxScale) {
// I don't need a minScale, as I use the initial width for that
this.el = el;
this.vwConversion = (100 / document.documentElement.clientWidth);
this.startingWidth = el.outerWidth();
this.maxScale = maxScale;
this.max = $(window).outerWidth() * this.maxScale;
this.step = (this.max - this.startingWidth) / $(window).outerHeight();
// for the sake of clarity, store a reference to `this` for
// any nested functions.
var that = this;
/**
* function scaleEl
* handle the actual scaling of the element.
* Using a given step, we will simply add that
* to the element's current width, then update the CSS
* width property of the element.
**/
this.scaleEl = function() {
// First, calculate the percentage of vertical scroll
var winheight = $(window).height();
var docheight = $(document).height();
var scrollTop = $(window).scrollTop();
var trackLength = docheight - winheight;
// gets percentage scrolled (ie: 80 NaN if tracklength == 0)
var pctScrolled = Math.floor(scrollTop / trackLength * 100);
// console.log(pctScrolled + '% scrolled')
// Now, using the scrolled percentage, scale the div
var tempWidth = this.startingWidth * this.vwConversion;
tempWidth += pctScrolled * this.step;
// I want to fix the max of the scale
if (tempWidth > (this.maxScale * 100)) {
tempWidth = this.maxScale * 100;
}
this.el.css('width', tempWidth + 'vw');
};
$(window).on("scroll", function() {
that.scaleEl();
}).on("resize", function() {
/**
* In the case of a resize, we should
* recalculate min, max and step.
**/
that.min = $(window).outerWidth() * that.minScale;
that.max = $(window).outerWidth() * that.maxScale;
that.step = (that.max - that.min) / $(window).outerHeight();
})
}
body {
height: 10000px;
}
.grow {
position: fixed;
height: 100vh;
top: 0;
left: 0;
}
.grow.gris {
width: 35vw;
z-index: 2;
background: silver;
}
.grow.naranja {
width: 10vw;
z-index: 1;
background: orange;
}
<script src="https://code.jquery.com/jquery-3.1.1.min.js" crossorigin="anonymous"></script>
<div class="grow naranja"></div>
<!-- .naranja -->

Why is my jQuery resize firing inconsistently?

I'm wrapping up a site that involves a few elements (image / text / diagonal line) that have to scale proportionately on different screens.
Because there's text that has to be resized, I'm using jQuery to calculate the measurements for all of the elements based on a ratio. This was the best solution I could think of at the time, and with a deadline approaching, I think I'm stuck with it. It's a single-page site that scrolls by the page (e.g., full pages in the viewport).
Here's a link to the demo site
The idea behind the code:
We check the height of the viewport to set the container size
Set the wrapper element height, based on the container size and necessary
margins
Set the width based on a ratio
Use these values to calculate font size, image size, and offsets
As the screen is re-sized, the element shrinks proportionately to fill the available space.
It looks kind of like this:
There are two panels like this. I re-use the same code (with different variable names, and a few sizing differences) for the second panel.
Here's my Javascript/jQuery for the first:
// Set panel height on page load & resize
$(window).on("resize", function () {
var $panelHeight = $(window).height();
var $headerHeight = $('.banner').height();
// General height for panels
$('.bg-panel').css('height', $panelHeight );
$('.bg-panel').css('padding-top', $headerHeight);
}).resize();
// We want to scale content proportionately
// First let's get some breakpoints
var $breakPoint = 768;
var $breakPointSM = 480;
// Panel 1
$(window).on("resize", function () {
// Check height of current panel
// If on single-column view, we want to measure the space between the text column and bottom of screen
// Otherwise, height of entire panel
var $windowHeight = $('.panel-test').height();
// But we need to subtract the header height, so our math is correct
var $headerHeight = $('.banner').height();
var $windowHeight = $windowHeight - $headerHeight;
// Now we have the correct height to work with
// We're at 768px or below, subtract the text element from the overall height
if ( $(document).width() <= $breakPoint) {
var $heightofDiv = $('.panel-1-text').height();
var $mobileHeight = $windowHeight - $heightofDiv;
var $windowHeight = $mobileHeight;
}
// Save the window height for calculating our margins!
var $windowHeightforMargins = $windowHeight;
// Top and bottom margins
var $marginTop = $windowHeight * (102/792); // ratio from PSD
var $marginBottom = $windowHeight * (84/792); // ratio from PSD
var $marginTotal = $marginTop + $marginBottom;
// Responsive solution
// As browser shrinks, reduce the height of panel so it produces a smaller container
if ( $(document).width() > 1200 && $(document).width() <= 1440) {
var $windowHeight = $windowHeight * 0.9;
var $marginTop = $marginTop * 2;
}
else if ( $(document).width() > 990 && $(document).width() <= 1200) {
var $windowHeight = $windowHeight * 0.8;
var $marginTop = $marginTop * 3;
}
else if ( $(document).width() > $breakPoint && $(document).width() <= 990) {
var $windowHeight = $windowHeight * 0.7;
var $marginTop = $marginTop * 3.5;
}
else if ( $(document).width() < $breakPoint) { // Ratio here goes up again because we're accounting for new height with $mobileHeight
var $windowHeight = $windowHeight * 0.8;
}
// This ratio determines the width of the container
var $ratio = 697 / 607; // from PSD
// Set container height, depending on height of panel
if ( $(document).width() <= $breakPointSM) {
var $taglinesHeight = ($windowHeight * 1.5); // Scale up for phones
}
else if ( $(document).width() > $breakPointSM && $(document).width() <= $breakPoint ){
var $taglinesHeight = ($windowHeight * 1); // Scale down for tablet
}
else {
var $taglinesHeight = $windowHeight - $marginTotal;
}
// Set container width as ratio of height
if ( $(document).width() <= $breakPoint) {
var $taglinesWidth = $taglinesHeight * $ratio
} else {
var $taglinesWidth = $taglinesHeight * $ratio
}
$('.panel-test .bg-taglines').css("width", $taglinesWidth);
$('.panel-test .bg-taglines').css("height", $taglinesHeight);
// Add top margin if above breakpoint
if ( $(document).width() > $breakPoint) { // No margin unless above 768px
$('.panel-test .bg-taglines').css("margin-top", $marginTop);
}
else {
$('.panel-test .panel-1-tagline').css("bottom", $marginTop);
}
// Set font size
var $fontSize = $taglinesWidth * 0.12;
$('.bg-panel h4').css("font-size", $fontSize);
// Set pink line origin (relative to bottom-left of frame)
var $pinkX = $taglinesWidth * (286 / 705);
var $pinkY = $taglinesHeight * (192 / 607);
$('.panel-test .animation-wrapper').css("left", $pinkX);
$('.panel-test .animation-wrapper').css("bottom", $pinkY);
// Set image size
var $imageWidth = $taglinesWidth * 0.556;
$('.panel-test .scaleable-image').css("width", $imageWidth);
// Set h3 margin from top
if ( $(document).width() >= $breakPoint) {
var $marginH3 = $windowHeight * (217/792); // ratio from PSD
$('.panel-test h3').css("margin-top", $marginH3);
} else {
// CSS
}
// Set line offset from top
var $lineOffset = $taglinesHeight * 0.7;
$('.panel-test .line-wrapper').css("top", $lineOffset);
// Set line length
var $lineLong = $taglinesWidth * 1;
$('.panel-test .pink-line').css("width", $lineLong);
}).resize();
It works: MOST of the time.
If I drag my window to resize, some of the elements get resized. Others don't.
A page refresh generally solves it, but right now, elements (mostly the images!) just aren't scaling properly and in sync with other elements.
I'm very new to jQuery and this is my first big undertaking. New to using resize as well. Hoping I just made a goof that's easy to fix.
Thanks!
LIVE SITE LINK
Other plugins in use: jQuery Scrollify (for full page scrolling) and ScrollReveal.
Guess I can answer my own question.
The issue seemed to be that the values were getting mixed up when scrolling from one full-screen panel to another.
Changing this:
$(window).on("resize", function () {
To this:
$(window).on("resize load scroll", function (e) {
... solved the issue. I'm not sure if it's the right way to do it, but the resizes are all working fine now.

How properly calculate and place an image on top of another image when they're both scaled - cross browser?

I have a base image with items on it. If the user disables one of the items, it should then show as disabled. To handle this, I use a second image of the item disabled (a png) and place that in the correct location above the background image.
When there's no scaling, this works perfectly across all browsers.
When there's scaling (for example, in a mobile browser when the image is 40% of the original size) this only works in Firefox. Firefox always places the image in the correct spot, covering the item underneath.
The other browsers (Chrome on Android, Safari iOS), are always off by 1-2 pixels depending on the scaling level. So you can see a little bit of the item below.
How can I do this so it works cross-browser?
var heightRatio = bgOffsetHeight / originalHeight;
var widthRatio = bgOffsetWidth / originalWidth;
//Place the disabled items
for ( var i = 0; i < disableds.length; i++ )
{
var disabled = disableds[ i ];
/**
* I also tried with "parseInt", but it still doesn't work.
*
* originalLeft/Top/Width/Height are the values saved in
* JavaScript when the page is first loaded before everything
* is scaled.
*/
disabled.style.left = ( widthRatio * disabled.originalLeft ) + "px";
disabled.style.top = ( heightRatio * disabled.originalTop ) + "px";
disabled.style.width = ( widthRatio * disabled.originalWidth ) + "px";
disabled.style.height = ( heightRatio * disabled.originalHeight ) + "px";
//Make visible
disabled.style.visibility = "visible";
}
Does your background use white or another solid color? Then you could try this: place an empty div over the bg portion and then apply background-color: rgba(255, 255, 255, .5) for example. This should give you the same effect, plus it would save a whole lot of additional images. But only works if "the bg of your bg" is a solid color.
The issue arises in WebKit only, where separately calculating the width and height ratio gives the browser an inability to properly size and place images. If I change my code to only use the width ratio, then everything places correctly across all browsers:
//We no longer use height ratio for the height/top calculations
//var heightRatio = bgOffsetHeight / originalHeight;
var widthRatio = bgOffsetWidth / originalWidth;
//Place the disabled items
for ( var i = 0; i < disableds.length; i++ )
{
var disabled = disableds[ i ];
/**
* I also tried with "parseInt", but it still doesn't work.
*
* originalLeft/Top/Width/Height are the values saved in
* JavaScript when the page is first loaded before everything
* is scaled.
*/
disabled.style.left = ( widthRatio * disabled.originalLeft ) + "px";
disabled.style.top = ( widthRatio * disabled.originalTop ) + "px";
disabled.style.width = ( widthRatio * disabled.originalWidth ) + "px";
disabled.style.height = ( widthRatio * disabled.originalHeight ) + "px";
//Make visible
disabled.style.visibility = "visible";
}

Adjust position of a group of adjacent divs in non-linear fashion

This isn't so much a jQuery question as it is an overall conceptual question.
In my example I can populate a container with divs that have a top value set in a nonlinear fashion.
The top value of each one is calculated based on a formula that takes into account the top position of the one to its left as well as the height of the container (line 33 of fiddle).
//this formula sets the top value for each new child added to the container
//height is 100% of its parent which is 20% of the body
//newOne:last is the most recently added child and will have an initial top value of 10%
parseInt($(this).next().css('top'), 10) / $('#queue').height()) * 75 + (parseInt($('.newOne:last').css('top'), 10) * 2) + '%'
I more of less stumbled upon this by chance and it seems to work 'ok', but if an optimization is obvious to you, please point it out :)
What I'm having trouble coming up with is an elegant formula for how to adjust the children smoothly during a drag event. I'm thinking the top value needs to be adjusted based on some manipulation of the left offset, but after hours of experimenting, I haven't found anything that keeps the original position intact when I start dragging and continues adjusting the values smoothly during my drag. The children should gradually approach a minimum top value of 10% as I drag left (child with left offset of 0 will have a top value of 10%), and gradually move away from that top value back toward their initial position as I drag right.
$('#queue').draggable({
axis: "x",
scroll: false,
drag: function(){
//adjust values of each child
$('.newOne').each(function(){
var percentLeft = $(this).offset().left / $('footer').width() * 100
var thisLeft = parseInt($(this).css('left'), 10) / $(window).width() * 100;
var thisTop = parseInt($(this).css('top'), 10) / $('#queue').height() * 100;
if (percentLeft >= 0){
//top value of each one gradually decreases...
//as it gets closer to an offset value of 0 and minimum top value of 10%
//non-linear attempt but not even close
//$(this).css('top', $(this).css('top', 10 + (thisTop - 10 / thisLeft) + '%'));
//linear step
$(this).css({'top': 8 + (percentLeft/2) + '%'});
}
});
}
});
http://jsfiddle.net/5RRCS/17/
P.S. I know I'm asking a lot here, but hopefully someone is up to the challenge :)
Update:
Stumbled onto exp method and did something like this:
adjustTop = function(offset){
return 100 * (1.0-Math.min(0.98,(0.83 + ( 0.17/ (Math.exp(0.007*offset))) )) ) + '%';
};
$(this).css('top', adjustTop($(this).offset().left) );
Here's a version that I believe does what you are looking for.
The first thing I did was to refactor the top calculation so that both the initialization and the drag handlers would get the same results.
Rather than calculate the positions of the child divs based on their offset to the document, I changed the logic to use position relative to their container.
I also remove z-index as the child divs already being added the parent with the correct stacking order - the left most child is the last element in the container.
Calculating the height of each child depended on whether #queue's current position was to the left or right of its origin.
I also change the iteration logic to behave the same to simplify calculating the current elements starting offset:
$($('.newOne').get().reverse()).each(function (index) {
$(this).css({
'background': 'rgba(255,255,255,.80)',
'top': calcTop($(this), index)
});
});
Code for positioning the child elements:
function calcTop($ele, index) {
var elePositionLeft = $ele.position().left;
var queuePositionLeft = $('#queue').position().left;
var footerWidth = $('footer').width();
var queueHeight = $('#queue').height();
var distanceToTravel = queuePositionLeft < 0 ? elePositionLeft : footerWidth - elePositionLeft;
var percentTraveled = Math.abs(queuePositionLeft) / distanceToTravel;
var thisPercentLeft = (elePositionLeft + queuePositionLeft) / footerWidth;
var queuePercentLeft = queuePositionLeft / footerWidth;
var newTop;
var myStartOffset = (index + 1) * startOffset;
var topTravel = queuePositionLeft < 0 ? -myStartOffset + startOffset : (queueHeight - startOffset);
var linear = false;
if (linear) {
newTop = myStartOffset + (topTravel * percentTraveled);
newTop = newTop > startOffset ? Math.round(newTop) : startOffset;
return newTop;
} else {
if (queuePositionLeft >= 0) {
newTop = myStartOffset + (topTravel * thisPercentLeft * percentTraveled);
newTop = newTop > startOffset ? Math.round(newTop) : startOffset;
} else {
newTop = myStartOffset + (topTravel * (1+thisPercentLeft) * percentTraveled);
newTop = newTop < startOffset ? startOffset : Math.round(newTop);
}
return newTop;
}
}
There was also a minor bug in the reset function - it wasn't setting childCount back to zero:
$('#reset').click(function () {
$('#queue').empty().css('left', 0);
childCount = 0;
});
Demo Fiddle

Categories

Resources