Expand/retract blinking bug in JavaScript - javascript

I have facing this bug, I have a script in JavaScript which handle actions on mousedown event. one do expanding and one do retracting the element using the JavaScript. It's done by timeloop and increasing the height of the div till the height fits the content or decreasing till the div is hidden. When you click on extract, wait till it extract and then click retract all works fine. The problem occurs when you quickly click extract and then retract (before the extraction ends). Then the magic bug appears. The actions is stuck in extracting and re-tracking one step and never ends.
I thought the problem should be with the loop variables (loop end condition).
Does anyone see where the problem rly is and how it could be fixed?
HERE'S THE CODE:
If you just copy the code into: something.html and extract_retract.js you can see the problem I'm dealing with.
Html document:
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
p{ padding:10px 20px; background:#D9ECFF; }
div.mydivs {
background: #97D6FB;
width: 500px;
height:0px;
overflow:hidden;
}
div.mydivs > p{ padding:4px 16px; background: #97D6FB;}
</style>
<script type="text/javascript" src="expand_retract.js"></script>
</head>
<body>
<h3>Programming Raw JavaScript expand() and retract() Animation Functions</h3>
<p>
Expand Box 1 |
Retract Box 1
</p>
<div id="div1" class="mydivs">
<p>Box 1 Content</p>
<p>Box 1 Content</p>
<p>Box 1 Content</p>
</div>
<p>
Expand Box 2 |
Retract Box 2
</p>
<div id="div2" class="mydivs">
<p>Box 2 Content</p>
<p>Box 2 Content</p>
<p>Box 2 Content</p>
<p>Box 2 Content</p>
<p>Box 2 Content</p>
</div>
</body>
</html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Dokument bez názvu</title>
</head>
<body>
</body>
</html>
expand_retract.js code below:
function expand(element){
var target = document.getElementById(element);
var h = target.offsetHeight;
var sh = target.scrollHeight;
var loopTimer = setTimeout('expand(\''+element+'\')',8);
if(h < sh){
h += 5;
} else {
clearTimeout(loopTimer);
}
target.style.height = h+"px";
}
function retract(element){
var target = document.getElementById(element);
var h = target.offsetHeight;
var loopTimer = setTimeout('retract(\''+element+'\')',8);
if(h > 0){
h -= 5;
} else {
target.style.height = "0px";
clearTimeout(loopTimer);
}
target.style.height = h+"px";
}

Great code. I propose this to solve this issue:
var loopTimer = 0;
function expand(element) {
clearInterval(loopTimer);
loopTimer = setInterval('expandA(\''+element+'\')',8);
}
function expandA(element){
var target = document.getElementById(element);
var h = target.offsetHeight;
var sh = target.scrollHeight;
if(h < sh){
h += 5;
} else {
clearInterval(loopTimer);
}
target.style.height = h+"px";
}
function retract(element) {
clearInterval(loopTimer);
loopTimer = setInterval('retractA(\''+element+'\')',8);
}
function retractA(element){
var target = document.getElementById(element);
var h = target.offsetHeight;
if(h > 0){
h -= 5;
} else {
target.style.height = "0px";
clearInterval(loopTimer);
}
target.style.height = h+"px";
}

Related

Animation and transition property with setTimeout() function

I'm fairly new to JS, and I'm trying to make a simple text slide animation. However, when the animation event is activated the first time, the transition property doesn't apply. Every time after that it works fine.
So I'm struggling with the first execution of the slide animation.
Here is the code:
function slide_animation(direction){
// Init
let text = document.querySelector(".story .box .right-part .text");
let text_html = text.innerHTML;
let text_slide = [
"Text n°1",
'Text n°2',
'Text n°3'
];
let current_slide = 0;
// Looking for the current_slide
for(let i=0;i<text_slide.length;i++){
if (text_slide[i]==text_html){current_slide = i};
}
// Calculating the next slide position
if (direction=='right'){
if (current_slide >= text_slide.length-1){
current_slide = 0;
}
else {current_slide+=1;}
}
else {
if (current_slide <= 0){
current_slide = text_slide.length-1;
}
else {
current_slide = current_slide-=1;
}
}
// Animation
setTimeout(()=>{
text.style.transition = '0.5s';
text.style.opacity = 0;
text.style.left = '100px';
},250);
setTimeout(()=>{
text.innerHTML = text_slide[current_slide];
text.style.opacity = 1;
text.style.left = '0px';
},750)
}
.story .box .right-part .text-container .text {
line-height: 1.25;
position: relative;
}
<div class="box">
<div class="left-part">
<div class="text-container">
<p class="text">
<span class="text-padding fontsize-md">----</span>
</p>
</div>
<div class="buttons-container">
<button class="backward" onclick="slide_animation('left')"></button>
<button class="forward" onclick="slide_animation('right')"></button>
</div>
</div>
<div class="right-part">
<div class="text-container">
<p class="text fontsize-sm">Text n°1</p>
</div>
</div>
</div>
Anyway , it is fixed. I added another setTimeOut that did nothing really useful on the text , i placed it just before the others setTimeOut and it seems to work out properly.
Here is what is inserted :
setTimeout(()=>{
text.style.left=0;
},0)

Change CSS of inner div when scroll reaches that div

I am attempting to implement a scroll function where the CSS of the inner div's change when it reaches a certain height from the top.
var $container = $(".inner-div");
var containerTop = $container.offset().top;
var documentTop = $(document).scrollTop();
var wHeight = $(window).height();
var minMaskHeight = 0;
var descriptionMax = 200;
var logoMin = -200;
var maskDelta = descriptionMax - minMaskHeight;
var $jobOverview = $container.find(".right");
var $jobLogo = $container.find(".left");
var curPlacementPer = ((containerTop - documentTop) / wHeight) * 100;
var topMax = 85;
var center = 20;
var bottomMax = -15;
//console.log("Placement: " + curPlacementPer);
function applyChanges(perOpen) {
var maskHeightChange = maskDelta * (perOpen / 100);
var opacityPer = perOpen / 100;
var newDescriptionLeft = descriptionMax - maskHeightChange;
var newLogoLeft = logoMin + maskHeightChange;
if (newDescriptionLeft <= 0) newDescriptionLeft = 0;
if (newLogoLeft >= 0) newLogoLeft = 0;
if (opacityPer >= 1) opacityPer = 1;
$jobOverview.css({
transform: "translate(" + newDescriptionLeft + "%,-50%)",
opacity: opacityPer
});
$jobLogo.css({
transform: "translate(" + newLogoLeft + "%,-50%)",
opacity: opacityPer
});
}
if (window.innerWidth > 640) {
$container.removeClass("mobile");
// console.log("Placement: " + curPlacementPer);
if (curPlacementPer <= topMax /*&& curPlacementPer >= center*/ ) {
var perOpen = ((topMax - curPlacementPer) / 25) * 100;
applyChanges(perOpen);
} else if (curPlacementPer < center /*&& curPlacementPer >= bottomMax*/ ) {
var perOpen = (((bottomMax - curPlacementPer) * -1) / 25) * 100;
applyChanges(perOpen);
} else {
$jobOverview.css({
transform: "translate(200%,-50%)",
opacity: "0"
});
$jobLogo.css({
transform: "translate(-300%,-50%)",
opacity: "0"
});
}
<div class="outer-div">
<div class="inner-div first">
<div class="left"></div>
<div class="right"></div>
</div>
<div class="inner-div second">
<div class="left"></div>
<div class="right"></div>
</div>
<div class="inner-div third">
<div class="left"></div>
<div class="right"></div>
</div>
<div class="inner-div fourth">
<div class="left"></div>
<div class="right"></div>
</div>
</div>
Currently, all of the inner div's gets changed at the same time.
I noticed that when I change the $container class to equal '.first' and specify it more, it works.
Is there any way to make the inner div's change separately, relative to its height from the top? Any way I can iterate the scroll function so I can add more inner div's in the future and not have to worry about changing my scroll function?
In raw JavaScript, this is my answer:
// Define the element -- The '#fooBar' can be changed to anything else.
var element = document.querySelector("#fooBar");
// Define how much of the element is shown before something happens.
var scrollClipHeight = 0 /* Whatever number value you want... */;
// Function to change an element's CSS when it is scrolled in.
const doSomething = function doSomething() {
/** When the window vertical scroll position plus the
* window's inner height has reached the
* top position of your element.
*/
if (
(window.innerHeight + window.scrollY) - (scrollClipHeight || 0) >=
element.getBoundingClientRect().top
)
// Generally, something is meant to happen here.
element.style = "/* Yay, some CSS! */"
};
// Call the function without an event occurring.
doSomething();
// Call the function when the 'window' scrolls.
addEventListener("scroll", doSomething, false)
This is the method I use. If there are other methods, I'd love to see them as well but this is my answer for now.
consider using 3rd party jQuery plugin for easier job, like one of these:
https://github.com/xobotyi/jquery.viewport
or
https://github.com/zeusdeux/isInViewport
then you can have additional element selector e.g.: ":in-viewport"
so you can:
$(window).on('scroll',function() {
$('div').not(':in-viewport').html('');
$('div:in-viewport').html('hello');
});
Check if current scroll offset from top is bigger than the element offset from the top:
$(window).scroll(function() {
var height = $(window).scrollTop();
var element = $('#changethis'); //change this to your element you want to add the css to
if(height > element.offset().top) {
element.addClass('black'); //add css class black (change according to own css)
}
});
Html:
<div id="changethis">Test</div>
Css:
body
{
height:2000px;
}
.black
{
background-color:black;
color:white;
padding:20px;
}
Demo:
https://codepen.io/anon/pen/WZdEap
You could easily implement this in your existing code.
Below is the sample snippet code, Hope it'll work for you:
$(document).ready(function(){
topMax = 100;
topMin = 25;
$(document).scroll(function(){
$('.inner-div').each(function(){
if($(this).offset().top-$(window).scrollTop()<=topMax && $(this).offset().top-$(window).scrollTop()>=topMin){
$(this).css({'background':'#c7c7c7'});
}else{
$(this).css({'background':'inherit'});
}
});
});
});
div{
width:100%;
border:1px solid red;
padding:5px;
}
div.inner-div{
border: 1px dashed green;
height: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="outer-div">
<div class="inner-div first">
<div class="left"></div>
<div class="right"></div>
</div>
<div class="inner-div second">
<div class="left"></div>
<div class="right"></div>
</div>
<div class="inner-div third">
<div class="left"></div>
<div class="right"></div>
</div>
<div class="inner-div fourth">
<div class="left"></div>
<div class="right"></div>
</div>
</div>
Happy to help you! :)

Animation gets fail

i have a script here that animate 3 divs containing product came from the database. It animate well when the page load in the first time but my problem is its not animate well after a min and everytime i refresh the page.
<html>
<script src='jquery.js'></script>
<script>
$(document).ready(function(){
$("#Slide2").hide();
$("#Slide3").hide();
});
var h = 1;
var x = setTimeout(show2, 3000);
function show1()
{
h = 1;
$("#Slide1").show();
$("#Slide2").hide();
$("#Slide3").hide();
$("#Slide1").css("left", "-400px");
$("#Slide1").animate({left:'-1px'});
clearTimeout(x);
x = setTimeout(show2, 3000);
}
function show2()
{
if(h <= 2)
{
h = 2;
$("#Slide2").show();
$("#Slide1").hide();
$("#Slide3").hide();
$("#Slide2").css("right", "-400px");
$("#Slide2").animate({right:'-1px'});
clearTimeout(x);
x = setTimeout(show3, 3000);
}
else
{
h = 2;
$("#Slide2").show();
$("#Slide1").hide();
$("#Slide3").hide();
$("#Slide2").css("left", "-400px");
$("#Slide2").animate({left:'-1px'});
clearTimeout(x);
x = setTimeout(show3, 3000);
}
}
function show3()
{
h = 3;
$("#Slide3").show();
$("#Slide2").hide();
$("#Slide1").hide();
$("#Slide3").css("right", "-400px");
$("#Slide3").animate({right:'-1px'});
clearTimeout(x);
x = setTimeout(show1, 3000);
}
</script>
<style>
#Slide1 , #Slide2, #Slide3
{
width:530px;
height:100px;
position:relative;
background:blue;
}
#container
{
width:500px;
margin:auto;
border:1pt solid black;
overflow:hidden;
height:auto;
}
</style>
<body>
<div id='container'>
<button onclick="check1()">1</button>
<button onclick="check2()">2</button>
<button onclick="check3()">3</button>
<br></br>
<div style='width:530px;margin:auto'>
<div id='Slide1'>
<div style='width:120px;height:100px;float:left;margin-left:10px;background:#ff0'></div>
<div style='width:120px;height:100px;float:left;margin-left:10px;background:#ff0'></div>
<div style='width:120px;height:100px;float:left;margin-left:10px;background:#ff0'></div>
<div style='width:120px;height:100px;float:left;margin-left:10px;margin-right:10px;background:#ff0'></div>
</div>
<div id='Slide2' >
<div style='width:120px;height:100px;float:left;margin-left:10px;background:green'></div>
<div style='width:120px;height:100px;float:left;margin-left:10px;background:green'></div>
<div style='width:120px;height:100px;float:left;margin-left:10px;background:green'></div>
<div style='width:120px;height:100px;float:left;margin-left:10px;margin-right:10px;background:green'></div>
</div>
<div id='Slide3' >
<div style='width:120px;height:100px;float:left;margin-left:10px;background:red'></div>
<div style='width:120px;height:100px;float:left;margin-left:10px;background:red'></div>
<div style='width:120px;height:100px;float:left;margin-left:10px;background:red'></div>
<div style='width:120px;height:100px;float:left;margin-left:10px;margin-right:10px;background:red'></div>
</div>
</div>
</div>
</body>
</html>
You are not clearing your previous setInterval before starting new setInterval. Use clearInterval(x) before every setInterval in your show1(), show2() and show3() . hope it ll help.
You have redundant if else loop in your show2() . check it here (http://jsfiddle.net/cJ9FK/)
Hope it'll help, Thank you

Set absolute height (offsetHeight) of HTML containers that use CSS padding, margin and border by Javascript

I want to do something like setting offsetHeight (offsetHeight is a read only property) - fit 3 div ("d1", "d2", "d3") into one container ("c"):
<!DOCTYPE HTML>
<html>
<body>
<style type="text/css">
.c {
background-color:#FF0000;
overflow:hidden;
}
.d {
left:10px;
border:9px solid black;
padding:13px;
margin:7px;
background-color:#FFFF00;
}
</style>
<div class="c" id="c">
<div id="d1" class="d">text text text</div>
<div id="d2" class="d">text text text</div>
<div id="d3" class="d">text text text</div>
</div>
<script type='text/javascript'>
var h=600;
var hd = Math.floor(h/3);
var c = document.getElementById("c");
var d1 = document.getElementById("d1");
var d2 = document.getElementById("d2");
var d3 = document.getElementById("d3");
c.style.height=h +"px";
d1.style.height=hd +"px";
var hd2 = (2 * hd - d1.offsetHeight) +"px";
d1.style.height=hd2;
d2.style.height=hd2;
d3.style.height=hd2;
</script>
</body>
</html>
but - first: the boxes doesn’t fit perfect :-( and secondly the style is bad. Do you have a idea how to fit the 3 div ("d1", "d2", "d3") into one container ("c")?
=> also I dont know how to read the css properties "padding" and "margin"
alert(d1.style.paddingTop);
doesn't work (maybe because it is defined by css-class and not direct)
Thank you :-)
Best regards Thomas
Which browser your using and what DOCTYPE you have determines the default box model for block elements. Usually, the default is content-box, which means that the padding, border, and margin all add to the height/width, so you'll need to factor that into your calculations if you have the box model as content-box.
Another options is, you can change the box model to border-box using the box-sizing CSS property. This means that the padding and border are included in the height and width, and only the margin adds to them. In my opinion, this box model is usually a more convenient one for doing what I want, so I usually end up switching.
Reference:
https://developer.mozilla.org/En/CSS/Box-sizing
https://developer.mozilla.org/en/CSS/box_model
After some testing I figure out this solution:
(works with: Opera, Firefox and Google Chrome)
(box-sizing: doesn't work on Firefox when used JavaScript?!)
<!DOCTYPE HTML>
<html>
<body>
<style type="text/css">
.c {
background-color:#FF0000;
overflow:hidden;
margin:0px;
padding:0px;
}
.d {
left:10px;
border:13px solid black;
padding:7px;
margin-bottom:13px;
margin-top:4px;
background-color:#FFFF00;
}
</style>
<div class="c" id="c">
<div id="d1" class="d">text text text</div>
<div id="d2" class="d">text text text</div>
<div id="d3" class="d">text text text</div>
</div>
<script type='text/javascript'>
///////////////////////////////////////////
// see: http://stackoverflow.com/questions/1601928/incrementing-the-css-padding-top-property-in-javascript
function getStyle(elem, name) {
if (elem.style[name]) {
return elem.style[name];
}
else if (elem.currentStyle) {
return elem.currentStyle[name];
}
else if (document.defaultView && document.defaultView.getComputedStyle) {
name = name.replace(/([A-Z])/g, "-$1");
name = name.toLowerCase();
s = document.defaultView.getComputedStyle(elem, "");
return s && s.getPropertyValue(name);
}
else {
return null;
}
}
///////////////////////////////////////////
var c = document.getElementById("c");
var d1 = document.getElementById("d1");
var d2 = document.getElementById("d2");
var d3 = document.getElementById("d3");
var paddingY = parseInt(getStyle(d1, 'paddingTop'),10) + parseInt(getStyle(d1, 'paddingBottom'), 10);
var marginTop = parseInt(getStyle(d1, 'marginTop'),10);
var marginBottom = parseInt(getStyle(d1, 'marginBottom'),10);
var marginMax = Math.max(marginTop, marginBottom);
var borderY = parseInt(getStyle(d1, 'borderTopWidth'),10) + parseInt(getStyle(d1, 'borderBottomWidth'), 10);
var h=600;
var count=3;
var hd = Math.floor((h-marginMax*(count-1) - marginTop - marginBottom - (paddingY + borderY) *count) / count) ;
c.style.height=h +"px";
d1.style.height=hd +"px";
d2.style.height=hd +"px";
d3.style.height=hd +"px";
</script>
</body>
</html>

Absolute positioning breaks in IE8

I am working on developing an asp.net control that I need to be able to drop into other applications. The control is basically a custom dropdown in which a div gets displayed or hidden when another element is clicked.
The problem I am having is in trying to get the dynamic div to align below the element that gets clicked. I wrote a javascript function which should, in theory, allow me to specify two elements and the desired alignment and then move the second element to the correct position in relation to the first element.
I have three test cases which relate to places where I currently expect this control will be used, my current markup and javascript work in all three cases for IE7 but fails for one of the cases in FF3.5 and IE8-standards mode. I have been playing with this for a while and have yet to come up with an answer that fixes the problem case without breaking one of the others. (Note that 90+% of my users are on IE7 with a slow migration towards IE8)
I am looking for any suggestions other than adding a compatibility mode directive to the page, that does fix things in IE8 but I would prefer an alternative if one is possible since I may not always have control over where this is used. Here is an HTML doc which illustrates the relevant markup and javascript along with the test cases. Case three is the one which has problems, instead of aligning neatly under the input element the div is overlapping vertically and offset to the right by a distance equivalent to the width of the select element.
(Note that the real pages utilize a reset style sheet adapted from the one published by Eric Meyer, including/omitting this style sheet has no relevant effect on these test cases.)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title></title>
</head>
<body>
<script type="text/javascript">
var VAlign = { "top": -1, "center": 0, "bottom": 1 };
var HAlign = { "left": -1, "center": 0, "right": 1 };
function AlignElements(element1, vAlign1, hAlign1, element2, vAlign2, hAlign2) {
var List1 = BuildOffsetList(element1);
var List2 = BuildOffsetList(element2);
var Index1 = List1.length - 1;
var Index2 = List2.length - 1;
while (Index1 >= 0 && Index2 >= 0 && List1[Index1] == List2[Index2]) {
Index1--;
Index2--;
}
element2.style.top = "";
element2.style.left = "";
var OT1 = 0;
var OL1 = 0;
var OT2 = 0;
var OL2 = 0;
while (Index1 >= 0) {
OT1 += List1[Index1].offsetTop;
OL1 += List1[Index1].offsetLeft;
Index1--;
}
while (Index2 >= 0) {
OT2 += List2[Index2].offsetTop;
OL2 += List2[Index2].offsetLeft;
Index2--;
}
var top = (OT1 - OT2);
if (vAlign1 == VAlign.bottom) {
top += element1.offsetHeight;
} else if (vAlign1 == VAlign.center) {
top += (element1.offsetHeight / 2);
}
if (vAlign2 == VAlign.bottom) {
top -= element2.offsetHeight;
} else if (vAlign2 == VAlign.center) {
top -= (element2.offsetHeight / 2);
}
var left = (OL1 - OL2);
if (hAlign1 == HAlign.right) {
left += element1.offsetWidth;
} else if (hAlign1 == HAlign.center) {
left += (element1.offsetWidth / 2);
}
if (hAlign2 == HAlign.right) {
left -= element2.offsetWidth;
} else if (hAlign2 == HAlign.center) {
left -= (element2.offsetWidth / 2);
}
element2.style.top = top + "px";
element2.style.left = left + "px";
}
function BuildOffsetList(elelment) {
var I = 0;
var List = new Array();
var Element = elelment;
while (Element) {
List[I] = Element;
Element = Element.offsetParent;
I++;
}
return List;
}
</script>
Case 1
<div>
<div id="control1" style=" display:inline; position:relative;">
<div id="control1_div1" style="background-color:Blue; height:75px; width:150px; position:absolute;"></div>
<input id="control1_txt1" type="text" style="width:150px;" />
<script type="text/javascript">
AlignElements(document.getElementById("control1_txt1"), VAlign.bottom, HAlign.left, document.getElementById("control1_div1"), VAlign.top, HAlign.left);
</script>
</div>
</div>
<div style="height:100px;"></div>
Case 2
<div>
<div id="Nav" style="float:left; width:200px; height:150px; background-color:Aqua;"></div>
<div id="Content" style="margin-left:200px; height:150px; background-color:#ddd;">
<div style="margin-left:100px;">
<h5 style="float:left; margin-left:-100px; width:90px; margin-right:10px; text-align:right; font-weight:.9em;">Label</h5>
<div id="control2" style=" display:inline; position:relative;">
<div id="control2_div1" style="background-color:Blue; height:75px; width:150px; position:absolute;"></div>
<input id="control2_txt1" type="text" style="width:150px;" />
<script type="text/javascript">
AlignElements(document.getElementById("control2_txt1"), VAlign.bottom, HAlign.left, document.getElementById("control2_div1"), VAlign.top, HAlign.left);
</script>
</div>
</div>
</div>
</div>
<div style="height:100px;"></div>
Case 3
<div>
<select><option>something</option></select>
<br />
<select><option>something else</option></select>
<div id="control3" style=" display:inline; position:relative;">
<div id="control3_div1" style="background-color:Blue; height:75px; width:150px; position:absolute;"></div>
<input id="control3_txt1" type="text" style="width:150px;" />
<script type="text/javascript">
AlignElements(document.getElementById("control3_txt1"), VAlign.bottom, HAlign.left, document.getElementById("control3_div1"), VAlign.top, HAlign.left);
</script>
</div>
</div>
</body>
</html>
The third case is breaking apart because of the inline display of the parent div - it cause the relative position to have no effect as far as I know.
To test such case use float instead, here is working example:
http://jsfiddle.net/yahavbr/estYF/1/
Thanks to Shadow Wizard for getting me thinking in the right direction. Turns out that the issue is that my absolutely positioned elements do not move to their 0,0 point when I clear the top and left properties. If I change the code to explicitly put them at 0,0 before calculating the offset difference then everything works beautifully.
element2.style.top = "";
element2.style.left = "";
becomes
element2.style.top = "0px";
element2.style.left = "0px";

Categories