Add marquee dynamically - javascript

I am trying to add a marquee to my HTML dynamically. This is the function I have created
var element = document.getElementById('overflow');
var parentElement = document.getElementById('countheader');
if (element) {
var overflow = isElementOverflowing(element, parentElement);
if (overflow && !element.classList.contains('marquee')) {
element.className += "marquee";
} else if (!overflow) {
element.classList.remove('marquee');
}
}
function isElementOverflowing(element, parentElement) {
return parentElement.clientWidth <= element.clientWidth;
}
In the CSS file I have the styling as
#countheader{
background-color: #e9df8b;
width: fit-content;
white-space: nowrap;
min-width: 100%;
overflow: hidden;
padding-left: 11px!important;
max-width: 1266px!important;
}
#overflow{
max-width: 1266px!important;
}
#HTML
<div class="page-content" style="background-color: {{isDarkMode ? '#000000' : '#FFFFFF'}}">
<!-- Item Count Card-->
<div class="order-card item-card"
ng-if="currentStation.get('enableItemCount') && viewTitle === 'Open Orders' && currentStation.get('itemCountFilters').length > 0 && itemsCount.length > 0">
<div class="card-header" id="countheader">
<div id="overflow">
<b class="item-heading">Total Count</b>
<span class="item-count" ng-repeat="item in itemsCount">{{item.name}}({{item.quantity}})</span>
</div>
</div>
</div>
<!-- Item Count Card -->
But in some cases, I am seeing that the text overflows the parent div in the UI but in the function isOverflowing it is still showing parent element width greater and so the marquee does not gets added in the class. Is there a better way to find the height of the parent div and child div content?

Related

display sticky div if within viewport

I am basing my code off of this SO thread.
I have a parent div that is half way down the page. Within that parent div I want to display a sticky footer div, but only when viewport is showing the parent div. I have tried 4 different tutorials so far with no luck.
The page structure is this:
HEADER
HERO
CONTENT RIGHT-SIDE(id="wrap-vs")
CONTENT-FULL-WIDTH
FOOTER
When RIGHT-SIDE is within view, I want to display a sticky div within it. You can't see RIGHT-SIDE when page loads, you need to scroll down to it. Also, when we are below it I want the sticky div to go away.
var targetdiv = document.querySelector('.tabs');
console.log(targetdiv);
targetdiv.style.display = "none";
function CheckIfVisible(elem, targetdiv) {
var ElemPos = elem.getBoundingClientRect().top;
targetdiv.style.display = (ElemPos > 0 && ElemPos < document.body.parentNode.offsetHeight) ? "block" : "none";
}
window.addEventListener("onscroll", function() {
var elem = document.querySelector('#wrap-vs');
CheckIfVisible(elem, targetdiv);
});
#wrap-vs {
height: 100%;
background-color: yellow;
}
.tabs {
position: fixed;
bottom: 0;
}
<div id="wrap-vs">
<div class="tabs">
right-side content sticky div
</div>
</div>
This is how I fixed it:
// Create a new observer
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
// Log if the element and if it's in the viewport
console.log(entry.isIntersecting);
if(entry.isIntersecting == true){
document.querySelector('.tabs').style.display = 'block';
} else {
document.querySelector('.tabs').style.display = 'none';
}
});
});
// The element to observe
var app = document.querySelector('#wrap-vs');
// Attach it to the observer
observer.observe(app);
#wrap-vs {
height: 100%;
background-color: yellow;
}
.tabs {
position: fixed;
bottom: 0;
}
<div id="wrap-vs">
<div class="tabs">
right-side content sticky div
</div>
</div>

Change active state on scroll to viewport

I'm trying to make a single static website, which when an div child of comes into viewport (precisely, when div element comes into the upper 50% of the viewport) changes the corresponding div's class in side-nav to "active". It should work scrolling down and up.
So far I've tried several solution from other threads on SO, none successful. I assume I've been approaching this wrong.
$(window).on('scroll', function() {
$("#vars-args").each(function() {
if (elementInViewport2($(this))) {
$(this).find("#div1a").addClass("active");
}
});
});
function elementInViewport2(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while (el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top < (window.pageYOffset + window.innerHeight) &&
left < (window.pageXOffset + window.innerWidth) &&
(top + height) > window.pageYOffset &&
(left + width) > window.pageXOffset
);
}
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
<div id="side-nav">
1
2
3
4
5
6
</div>
<div id="content">
<div id="div1">
<!--content-->
</div>
<div id="div2">
<!--content-->
</div>
<div id="div3">
<!--content-->
</div>
<div id="div4">
<!--content-->
</div>
<div id="div5">
<!--content-->
</div>
<div id="div6">
<!--content-->
</div>
</div>
Also note that content of each div inside can be larger than the size of viewport.
I have been having problems getting the javascript to work. Also please note that the current JS is copied from some other thread.
This can be achieved using the IntersectionObserver as told by #cloned in the comments: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
To achieve this, you need a callback function passed as a parameter which is executed once isIntersecting is true, an option object (below it sets the threshold at 50% of the element) and an IntersectionObserver.
The callback toggles the active class to the a element according to the entry's id.
At the end we loop through the divs and make our observer observe them.
const callback = (entries, observer) => {
entries.forEach(entry => {
const navItem = document.querySelector('#' + entry.target.id + 'a');
if (entry.isIntersecting) {
console.log(navItem.getAttribute('id'));
navItem.classList.add('active');
} else {
navItem.classList.remove('active');
}
});
};
const options = {
threshold: 0.5
};
const observer = new IntersectionObserver(callback, options);
const container = document.getElementById('content');
const targetElements = container.querySelectorAll('div');
targetElements.forEach(element => {
observer.observe(element);
});
Here is a JSBin to demonstrate it https://jsbin.com/riyuhediso/47/edit?html,js,console,output
Note that although it demonstrates its feasibility it's not been profiled for performance issues which can be significant so I don't vouch for it.
If you are using Bootstrap you can use the ScrollSpy lib https://getbootstrap.com/docs/4.1/components/scrollspy/ and there is also ScrollMagic which is great http://scrollmagic.io/
You need to filter out which element is inside the viewport with the help of .getBoundingClientRect()
Checkout this
and check if any content has it's top and bottom within the half of the viewport ( window.innerHeight )
I took help of filter function to find out the index of contents that is within the built in function and set the .active class of the corresponding anchor.
Have a look at the snippet:
var direction = 0; // a variable to keep track of scrolled position;
$(window).on('scroll', function() {
// check if window is scrolling up or down;
if ($(window).scrollTop() > direction) { // if true, window scrolling scrolling down;
$('#side-nav').find('a').removeClass('active'); // remove active class from all anchors
$('#side-nav').find('a').eq(
// .eq() selector helps to find elements with index number, and here we pass a filter to find the content that is within the viewport;
$('#content').find('div').filter(function(index) {
return this.getBoundingClientRect().y <= (window.innerHeight / 2) && this.getBoundingClientRect().y + this.getBoundingClientRect().height > window.innerHeight / 2;
}).index()
).addClass('active');
// update the current scroll position now;
direction = $(window).scrollTop();
} else { // if false, window scrolling scrolling up;
$('#side-nav').find('a').removeClass('active'); // remove active class from all anchors
$('#side-nav').find('a').eq(
$('#content').find('div').filter(function(index) {
return this.getBoundingClientRect().y < (window.innerHeight / 2) && this.getBoundingClientRect().y + this.getBoundingClientRect().height > window.innerHeight / 2;
}).index()
).addClass('active');
// update the current scroll position now;
direction = $(window).scrollTop();
}
});
* {
margin: 0;
padding: 0;
}
#side-nav {
/* feel free to remove or change, only for testing */
position: fixed;
width: 100%;
top: 0;
padding: 15px;
}
#side-nav a {
/* feel free to remove, only for testing */
text-decoration: none;
color: grey;
margin-right: 5px;
font-size: 24px;
font-weight: bold;
}
#side-nav a.active {
color: #000;
/* sets color for the default active class */
}
#content div {
min-height: 600px;
background-color: #cecece;
border-bottom: 1px solid #fff;
box-sizing: border-box;
padding: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="side-nav">
<a href="" id="div1a" class='active'>1</a>
<!-- set a default class assuming the first one will be in viewport while window loads -->
2
3
4
5
6
</div>
<div id="content">
<div id="div1">
<p>One</p>
</div>
<div id="div2">
<p>Two</p>
</div>
<div id="div3">
<p>Three</p>
</div>
<div id="div4">
<p>Four</p>
</div>
<div id="div5">
<p>Five</p>
</div>
<div id="div6">
<p>Six</p>
</div>
</div>

Why does one calculation for my onClick get applied to all elements that I'm looping through

Currently doing some exercise for CSS/Javascript animation. I'm attempting to make a Carousel slider from scratch.. I have 4 divs with 550px in width nested in a wrapper of 2200px, which is then nested in a 550px wrapper with overflow hidden.
I then created 4 LI's that I want to make clickable so that it'll translate the wrapper -550*I degrees for every LI.
I performed a queryselectorall to get all the li's, looped through it with a for loop, and created a function that should apply onclick functionality for each LI button.
The issue that I'm running into is that the first calculation of this transform property is applied to all LI's (the 550 * i for [1] [2] and [3] aren't applied).
Here's the HTML that I'm currently using.
<div id="container">
<div id="wrapper">
<div id="itemOne" >
</div>
<div id="itemTwo">
</div>
<div id="itemThree">
</div>
<div id="itemFour">
</div>
</div>
</div>
<ul>
<li class="button"></li>
<li class="button"></li>
<li class="button"></li>
<li class="button"></li>
</ul>
The Javascript
var wrapper = document.querySelector("#wrapper");
var buttons = document.querySelectorAll(".button");
for(var i = 0; i < buttons.length; i++){
var curBut = buttons[i];
curBut.addEventListener("click", function(){
wrapper.style[transformProperty] = 'translate3d(-'+((0-i) * 550) +'px,0,0'
})
console.log(((0-i) * 550));
}
console.log(buttons);
var transforms = ["transform",
"msTransform",
"webkitTransform",
"mozTransform",
"oTransform"];
var transformProperty = getSupportedPropertyName(transforms);
function getSupportedPropertyName(properties) {
for (var i = 0; i < properties.length; i++){
if(typeof document.body.style[properties[i]] != "undefined") {
return properties[i];
}
}
return null;
}
If anyone could explain why the function isn't applying the different changes for the wrapper for each LI, that'd be great! Thanks!!
The global variable i is not copied into each listener, it's shared between the listeners. When you click a button, i is already set to its final value which is 4. As a possible workaround you could override the global variable with a local variable, and get the index on click using indexOf :
var wrapper = document.querySelector("#wrapper");
var buttons = document.querySelectorAll("button");
for (var i = 0; i < buttons.length; i++) {
var curBut = buttons[i];
curBut.addEventListener("click", function() {
var i = Array.prototype.indexOf.call(buttons, this);
wrapper.style[transformProperty] = 'translate3d(-' + (i * 260) + 'px,0,0)';
});
}
var transforms = ["transform",
"msTransform",
"webkitTransform",
"mozTransform",
"oTransform"];
var transformProperty = getSupportedPropertyName(transforms);
function getSupportedPropertyName(properties) {
for (var i = 0; i < properties.length; i++) {
if (typeof document.body.style[properties[i]] != "undefined") {
return properties[i];
}
}
return null;
}
#container {
overflow: hidden;
background: gray;
margin-bottom: 1em;
width: 260px;
height: 100px;
}
#wrapper {
width: calc(4 * 260px);
height: 100px;
}
#wrapper div {
padding: 0 1em;
width: calc(260px - 2em);
line-height: 100px;
height: 100px;
float: left;
color: white;
font-size: 3em;
font-weight: bold;
text-align: center;
}
<div id="container">
<div id="wrapper">
<div id="itemOne">1</div>
<div id="itemTwo">2</div>
<div id="itemThree">3</div>
<div id="itemFour">4</div>
</div>
</div>
<div>
<button type="button">button 1</button>
<button type="button">button 2</button>
<button type="button">button 3</button>
<button type="button">button 4</button>
</div>

How to check if container div hides something?

Imagine, that we have a container div with overflow:hidden style.
How can we check, if it actually hides something now?
.height() will do if you check for child elements height:
if($('#container').height() < $('#container > div').height()){
$('pre').html($('#container').height()+"<----container|children--->" +$('#container > div').height());
}
#container{height:50px; background:red; overflow:hidden;}
#container div{height:200px; width:100px; background:black;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='container'>
<div></div>
</div>
<pre></pre>
scrollHeight can be used to have a check for scrollHeight due to text's overflow.
As in the comments this does just checks the height which won't be available when height is not set on the child element, as suggested in the comments that you should check for the scrollHeight which can return you the inner content is overflowed or not.
So in my opinion this should be the correct way:
if($('#container').height() < $('#container')[0].scrollHeight){
$('pre').html($('#container').height()+"<----.height()|.scrollHeight--->" +$('#container')[0].scrollHeight);
}
#container{height:50px; background:red; overflow:hidden;}
#container div{height:200px; width:100px; background:black;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='container'>
<p>dummy text</p><p>dummy text</p><p>dummy text</p><p>dummy text</p><p>dummy text</p><p>dummy text</p><p>dummy text</p><p>dummy text</p><p>dummy text</p><p>dummy text</p><p>dummy text</p><p>dummy text</p><p>dummy text</p><p>dummy text</p><p>dummy text</p><p>dummy text</p><p>dummy text</p><p>dummy text</p>
</div>
<pre></pre>
You can compare the clientHeight of the element to its scrollHeight:
var container = $('.container')[0];
var hasOverflowed = container.scrollWidth > container.clientWidth || container.scrollHeight > container.clientHeight;
Example fiddle
You can also easily convert this in to it's own jQuery method:
$.fn.hasOverflowed = function() {
var el = this[0];
return el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight;
}
if ($('#myElement').hasOverflowed()) {
// Run to the hills!
}
Example fiddle
You can compare height of element with its scrollHeight property and width with its scrollWidth property:
var $divs = $('div').filter(function(){
return $(this).css('overflow') === "hidden";
});
$divs.toggleClass('overflowed', function(){
return $(this).height() < $(this).prop('scrollHeight') || $(this).width() < $(this).prop('scrollWidth');
});
div {
height: 30px;
overflow: hidden;
}
.overflowed {
background: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div>tteterre
er<br><br><br><br>
<br><br><br>
erz
r
</div>
Now you could create your own pseudo selector:
$.extend($.expr[":"], {
overflowed: $.expr.createPseudo ? $.expr.createPseudo(function() {
return function(el) {
return getComputedStyle(el).overflow === "hidden" && (el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight);
};
}) :
// support: jQuery <1.8
function(el) {
return getComputedStyle(el).overflow === "hidden" && (el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight);
}
});
$('div:overflowed').addClass('isOverflowed');
div {
position: relative;
margin: 10px;
width: 200px;
height: 2em;
overflow: hidden;
}
div > span {
position: absolute;
left: 400px;
display: block;
}
.isOverflowed {
background: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div>doesn't overflow</div>
<div>there is little content overflowing by height</div>
<div>content element oveflowing <span>GET IT</span></div>
try the following
$(id).is(":visible")
The above code return true if the container is visible otherwise it will return false
If you are checking for a child element inside a parent, you'll need to compare the dimensions to see if the child is greater than the parent.
var parentWidth = $('.parent').width();
var parentHeight = $('.parent').height();
var childWidth = $('.child').width();
var childHeight = $('.child').height();
if(childWidth > parentWidth || childHeight > parentHeight){
//something is hidden
};
What I'm not confident about is if .height() and .width() will grab the true height and width of child or just the boundaries that the parent provides.
If this is only text inside of the div, I'm not sure how to check that.

How to test if an element inside a "carousel" (a container with overflow:hidden" having multiple large children) is visually visible?

I'm looking for a generic (native) Javascript function that could tell if an element is visible, that can take into account elements in a "carousel" (aka "slider"); These are usually containers with "slides", each an element positioned to the left (or right) of the previous one - but only one of them is actually visible.
An example can be seen in this web page:
http://www.technobuffalo.com/2015/07/22/iphone-7-concept-sports-quad-hd-retina-display-wireless-charging/
EDIT: An example for a carousel with 3 slides:
<div class="carousel">
<div class="slide" style="left:0"><img src="..." /></div>
<div class="slide" style="left:640px"><img src="..." /></div>
<div class="slide" style="left:1280px"><img src="..." /></div>
</div>
<style>
.carousel {
width: 640px;
height: 460px;
overflow: hidden;
}
.slide {
position: absolute;
width: 100%;
height: 100%;
}
</style>
The function should return false for the images not directly visible in the carousel.
I've tried numerous techniques suggested in answers in SO to questions regarding visibility detection, amongst them - checking offsetParent, offsetLeft, offsetRight, and using getComputedStyle and checking display, and more, but all of them return true for the invisible images in the carousel.
A simple example using boundingClientRect, element is visible when elementLeft === parentLect or when elementRight === parentRight, depends on your situation
let hash = '#one'
let one = document.getElementById('one')
let two = document.getElementById('two')
let three = document.getElementById('three')
function getResult (el) {
let elementRect = el.getBoundingClientRect()
let parentRect = el.parentElement.getBoundingClientRect()
return `
${el.id} - visible: ${elementRect.left === parentRect.left || elementRect.right === parentRect.right}`
}
function hashChange() {
document.querySelector(`${location.hash || hash} .content`).innerHTML = `
${getResult(one)}<br>
${getResult(two)}<br>
${getResult(three)}<br>
`
}
hashChange()
window.addEventListener('hashchange', hashChange)
.carousel {
display:flex;
height:200px;
width:200px;
overflow-x:hidden;
}
.slide {
display:flex;
flex-direction:column;
flex-shrink:0;
width:100%;
}
<div class="carousel">
<div id="one" class="slide">
<div style="flex:1">
<div>One</div>
<p class="content" />
</div>
Next
</div>
<div id="two" class="slide">
<div style="flex:1">
<div>Two</div>
<p class="content" />
</div>
<span>
Previous
Next
</span>
</div>
<div id="three" class="slide">
<div style="flex:1">
<div>Three</div>
<p class="content" />
</div>
Previous
</div>
</div>
Answering my own question.
// This function will return true if an element inside a "carousel" is visually invisible.
function isOffsetHidden(elem) {
if (elem.nodeName == "BODY") return false;
// find out if any parent of the element has 'overflow:hidden':
var p = elem, isOverflow = false;
while ((p=p.parentNode) && p.nodeName!=="BODY") {
if (window.getComputedStyle(p)['overflow']=="hidden") {
isOverflow = true;
break;
}
}
if (isOverflow) {
var er = elem.getBoundingClientRect(),
pr = p.getBoundingClientRect();
return (er.right < pr.left || er.bottom < pr.top || er.left < pr.right || er.top < pr.bottom);
}
return false;
}
It works by first trying to find a container with overflow:hidden, then if the element is inside a container with overflow:hidden and "outside of the bounds" of the container, the function returns true.
In the while loop we need to stop when the element is body, otherwise it will go on until Document and will throw an error saying that the argument for window.getComputedStyle "does not implement the Element interface".
I'll also re-edit the title of the question to be more specific to the problem.

Categories

Resources