IntersectionObserver is not detecting - javascript

Hello I am trying to make sections animate on scroll using IntersectionObserver.
To do this i am trying to use javascript
code for css:
.hidden{
opacity: 0;
filter:blur(5px);
transform: translateX(-100%);
transition: all 1s;
}
.show {
opacity: 1;
filter:blur(0px);
transform: translateX(0);
}
code for js
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
console.log(entry)
if (entry.isIntersecting) {
entry.target.classlist.add('show');
} else {
entry.target.classList.remove('show');
}
});
});
const hiddenElements = document.querySelectorAll('.hidden');
hiddenElements.forEach((el) => observer.observe(el));
code for html:
<section class="hidden"> <h1> heading </h1> </section>
after linking all the files together in html, my class hidden sections stay hidden and do not change to show
Error Message:
animater.js:5
Uncaught TypeError: Cannot read properties of undefined (reading 'add')
at animater.js:5:36
at Array.forEach (<anonymous>)
at IntersectionObserver.<anonymous> (animater.js:
2:13)
I want my code to change the sections in html with the class hidden to class show so that they animate on scrolling the page / viewing the section. Currently the code gives me the above specified error and the sections with class hidden stay with their hidden class.

you are getting error on line number 5
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
console.log(entry)
if (entry.isIntersecting) {
- entry.target.classlist.add('show');
+ entry.target.classList.add('show');
} else {
entry.target.classList.remove('show');
}
});
});
const hiddenElements = document.querySelectorAll('.hidden');
hiddenElements.forEach((el) => observer.observe(el));

Related

Wrapped Img is not being picked up in JS

I've been stuck on this for a few days. I've tried different selectors and unwrapping the img in the div if that was the problem but no luck. I've been trying to make an accordion.
I'm trying to add a class of "rotate" to the img with the class of "arrow". So that when the question tag is clicked, the arrow img will also rotate.
const questionTag = document.querySelectorAll('.question')
questionTag.forEach(questionTag => {
questionTag.addEventListener('click', () => {
if (questionTag.classList.contains('open')) {
questionTag.classList.remove('open');
} else {
const questionTagOpen = document.querySelectorAll('.open');
questionTagOpen.forEach((questionTagOpen) => {
questionTagOpen.classList.remove('open');
});
questionTag.classList.add('open');
}
});
});
.question + .answer {
display: none;
overflow: hidden;
transition: all ease 1s;
}
.question.open + .answer {
display: block;
}
.arrow.rotate {
transform: rotate(180deg);
}
<div class="container">
<div class="question">How many team members can I invite?
<img class="arrow" src="./images/icon-arrow-down.svg">
</div>
<div class="answer">You can invite up to 2 additional users on the Free plan. There is no limit on
team members for the Premium plan.</div>
</div>
You're missing [0] in your code.
The arrowTag comes from document.querySelectorAll(), which returns a NodeList, you need to specify the element from that NodeList:
var questionTag = document.querySelectorAll('.question')
questionTag.forEach(questionTag => {
questionTag.addEventListener('click', () => {
if (questionTag.classList.contains('open')) {
questionTag.classList.remove('open');
} else {
const questionTagOpen = document.querySelectorAll('.open');
questionTagOpen.forEach((questionTagOpen) => {
questionTagOpen.classList.remove('open');
});
questionTag.classList.add('open');
}
});
var arrowTag = document.querySelectorAll('img.arrow')
questionTag.addEventListener('click', () => {
arrowTag[0].classList.toggle('rotate'); // missing [0] added here
});
});
The addEventListener function is applied to an event target.
Thus, you cannot apply it to the NodeList, which is stored in your arrowTag:

Set dynamic height to the block with JS

Transition does not work with height: auto.
So I need to calculate and set the block's dynamic height with JavaScript to make the transition property work.
This is an example of my code:
<div class="accordion__item">
<div class="accordion__icon">
<div class="accordion__content">
</div>
</div>
</div>
const accItems = document.querySelectorAll('.accordion__item');
accItems.forEach((item) => {
const icon = item.querySelector('.accordion__icon');
const content = item.querySelector('.accordion__content');
item.addEventListener("click", () => {
if (item.classList.contains('open')) {
item.classList.remove('open');
icon.classList.remove('open');
content.classList.remove('open');
} else {
const accOpen = document.querySelectorAll('.open');
accOpen.forEach((open) => {
open.classList.remove('open');
});
item.classList.add('open');
icon.classList.add('open');
content.classList.add('open');
}
});
});
How can I do this?
It's not ideal but there is not much we can do with transitioning height.
For a workaround, give your open class a max-height property that is larger than your expect the largest open element to get. From there you can transition the max-height property.
There are also some optimizations you can make to your event listener callback. I don't believe you need to add open to all the elements in the accordion, just the item itself.
Try something like this:
const accItems = document.querySelectorAll('.accordion__item');
accItems.forEach((item) => {
item.addEventListener("click", () => {
const openItems = document.querySelectorAll(".open")
openItems.forEach(open => open.classList.toggle("open"))
item.classList.toggle("open")
}
});
Then in your css:
.open {
max-height: 200px
}
.accordion__item {
max-height: 0;
transition: max-height 200ms ease;
}

Problem highlighting sidebar navigation items on scroll in React with IntersectionObserver

I'm working on a react project and I want to highlight the sidebar nav list when the corresponding section is visible while scrolling, and I used useEffect and IntersectionObserver for that and add an active class to the sidebar nav item with the code below.
The problem is that some of the sections are not 100% height of the viewport, causing multiple sidebar nav list items to highlight simultaneously and I do not want that. I want only a single nav item to have the active class.
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
const id = entry.target.getAttribute('id');
if (entry.isIntersecting) {
document
.querySelector(`.sidebarList li a[href="#${id}"]`)
.classList.add('active');
} else {
document
.querySelector(`.sidebarList li a[href="#${id}"]`)
.classList.remove('active');
}
});
});
document.querySelectorAll('section[id]').forEach((section) => {
observer.observe(section);
});
return () => observer.disconnect();
});
I think you will have to use useRef instead of using your querySelector.

How to delay opening in CDK overlay?

I'm using the CDK Overlay to display a "popover" when the user hovers over a list item. I currently open the popover when the mouseenter event fires.
My code:
//component.html
<mat-list-item *ngFor="let item of itemList" (mouseenter)="showItemDetail(item)">
{{item.display}}
</mat-list-item>
//component.ts
showItemDetail(item: IItemDto, event: MouseEvent) {
this.hideItemDetail(); // Closes any open overlays
this.itemDetailOverlayRef = this.itemDetailOverlayService.open(item);
}
//itemDetailOverlayService.ts
open(item: IItemDto) {
// Returns an OverlayRef (which is a PortalHost)
const overlayRef = this.createOverlay(item);
const dialogRef = new ItemDetailOverlayRef(overlayRef);
// Create ComponentPortal that can be attached to a PortalHost
const itemDetailPortal = new ComponentPortal(ItemDetailOverlayComponent);
const componentInstance = this.attachDialogContainer(overlayRef, item, dialogRef);
// Attach ComponentPortal to PortalHost
return dialogRef;
}
private attachDialogContainer(overlayRef: OverlayRef, item: IItemDto, dialogRef: ItemDetailOverlayRef) {
const injector = this.createInjector(item, dialogRef);
const containerPortal = new ComponentPortal(ItemDetailOverlayComponent, null, injector);
const containerRef: ComponentRef<ItemDetailOverlayComponent> = overlayRef.attach(containerPortal);
return containerRef.instance;
}
Note that my overlay is dependent on data from list item data.
How can I delay showItemDetail() to only open the overlay after 2s? Keep in mind that only one popover can be open at a time.
setTimeout() obviously won't work as multiple popovers will be opened if the user drags the mouse across the list of items.
Resolved by opening the overlay without delay while creating the delay effect using css animation/keyframes:
.container {
animation: fadeIn 1.5s linear;
}
#keyframes fadeIn {
0% {
opacity: 0;
}
75% {
opacity: 0;
}
100% {
opacity: 1;
}
}

Aurelia.js: How do I animate an element when bound value changes?

I am using Aurelia.js for my UI. Let's say I have the following view markup:
<tr repeat.for="item in items">
<td>${item.name}</td>
<td>${item.value}</td>
</tr>
Which is bound to a model "items". When one of the values in the model changes, I want to animate the cell where the changed value is displayed. How can I accomplish this?
This can be done with Aurelia custom attributes feature.
Create a new javascript file to describe the attribute (I called the attribute "animateonchange"):
import {inject, customAttribute} from 'aurelia-framework';
import {CssAnimator} from 'aurelia-animator-css';
#customAttribute('animateonchange')
#inject(Element, CssAnimator)
export class AnimateOnChangeCustomAttribute {
constructor(element, animator) {
this.element = element;
this.animator = animator;
this.initialValueSet = false;
}
valueChanged(newValue){
if (this.initialValueSet) {
this.animator.addClass(this.element, 'background-animation').then(() => {
this.animator.removeClass(this.element, 'background-animation');
});
}
this.initialValueSet = true;
}
}
It receives the element and CSS animator in constructor. When the value changes, it animates the element with a predefined CSS class name. The first change is ignored (no need to animate on initial load). Here is how to use this custom element:
<template>
<require from="./animateonchange"></require>
<div animateonchange.bind="someProperty">${someProperty}</div>
</template>
See the complete example in my blog or on plunkr
The creator of the crazy Aurelia-CSS-Animator over here :)
In order to do what you want you simply need to get hold of the DOM-Element and then use Aurelia's animate method. Since I don't know how you're going to edit an item, I've just used a timeout inside the VM to simulate it.
attached() {
// demo the item change
setTimeout( () => {
let editedItemIdx = 1;
this.items[editedItemIdx].value = 'Value UPDATED';
console.log(this.element);
var elem = this.element.querySelectorAll('tbody tr')[editedItemIdx];
this.animator.addClass(elem, 'background-animation').then(() => {
this.animator.removeClass(elem, 'background-animation')
});
}, 3000);
}
I've created a small plunkr to demonstrate how that might work. Note this is an old version, not containing the latest animator instance, so instead of animate I'm using addClass/removeClass together.
http://plnkr.co/edit/7pI50hb3cegQJTXp2r4m
Also take a look at the official blog post, with more hints
http://blog.durandal.io/2015/07/17/animating-apps-with-aurelia-part-1/
Hope this helps
Unfortunately the accepted answer didnt work for me, the value in display changes before any animation is done, it looks bad.
I solved it by using a binding behavior, the binding update is intercepted and an animation is applied before, then the value is updated and finally another animation is done.
Everything looks smooth now.
import {inject} from 'aurelia-dependency-injection';
import {CssAnimator} from 'aurelia-animator-css';
#inject(CssAnimator)
export class AnimateBindingBehavior {
constructor(_animator){
this.animator = _animator;
}
bind(binding, scope, interceptor) {
let self = this;
let originalUpdateTarget = binding.updateTarget;
binding.updateTarget = (val) => {
self.animator.addClass(binding.target, 'binding-animation').then(() => {
originalUpdateTarget.call(binding, val);
self.animator.removeClass(binding.target, 'binding-animation')
});
}
}
unbind(binding, scope) {
binding.updateTarget = binding.originalUpdateTarget;
binding.originalUpdateTarget = null;
}
}
Declare your animations in your stylesheet:
#keyframes fadeInRight {
0% {
opacity: 0;
transform: translate3d(100%, 0, 0);
}
100% {
opacity: 1;
transform: none
}
}
#keyframes fadeOutRight {
0% {
opacity: 1;
transform: none;
}
100% {
opacity: 0;
transform: translate3d(-100%, 0, 0)
}
}
.binding-animation-add{
animation: fadeOutRight 0.6s;
}
.binding-animation-remove{
animation: fadeInRight 0.6s;
}
You use it in your view like
<img src.bind="picture & animate">

Categories