I have a react app, which renders the data on the screen. The URL is of the format <DOMAIN>/slug#entityId. Thus, after the view is loaded, I need to scroll to that specific #entityId provided as the id of the HTML element.
I am using React's componentDidUpdate() method to scroll to the ID after the render occurs.
componentDidUpdate () {
if(// data rendered into view) {
const id = this.entityId;
if (id) {
setTimeout(() => {
const element = document.getElementById(id);
if (element) {
element.scrollIntoView({
behavior: 'smooth'
});
}
}, 500);
}
}
}
Thus, the scroll to the ID happens before the images are loaded. This leads to the scroll not stopping at the expected place. The number of images is their resolution is variable.
Increasing the timeout to 1000ms does solve the issue. But is this an optimal solution?
with document ready
$(document).ready(function () {
if(// data rendered into view) {
const id = this.entityId;
if (id) {
setTimeout(() => {
const element = document.getElementById(id);
if (element) {
element.scrollIntoView({
behavior: 'smooth'
});
}
}, 500);
}
}
}
});
Related
I'm appending a DOM element like this:
this.store.state.runtime.UIWrap.appendChild(newElement)
When I immediately try to measure the new element's width I get 2.
I tried:
setTimeout()
double nested window.requestAnimationFrame()
MutationObserver
The above works very unreliably, like 50% of the time. Only when I set a large timeout like 500ms it worked.
This happens only on mobile.
This is the workaround that I'm using, but it's ugly:
function getWidthFromStyle(el) {
return parseFloat(getComputedStyle(el, null).width.replace('px', ''))
}
function getWidthFromBoundingClientRect(el) {
return el.getBoundingClientRect().width
}
console.log(getWidthFromBoundingClientRect(newElement))
while (getWidthFromBoundingClientRect(newElement) < 50) {
await new Promise(r => setTimeout(r, 500))
console.log(getWidthFromBoundingClientRect(newElement))
}
I tried with both functions getWidthFromStyle() and getWidthFromBoundingClientRect() - no difference. The width gets calculated properly after a couple of cycles.
I also tried using MutationObserver without success.
Is there a way to know when the DOM is fully updated and styled before I try to measure an element's width/height?
P.S. I'm not using any framework. this.store.state.runtime... is my own implementation of a Store, similar to Vue.
EDIT: The size of the element depended on an image inside it and I was trying to measure the element before the image had loaded. Silly.
it can done with MutationObserver.
doesn't this method solve your problem?
const div = document.querySelector("div");
const span = document.querySelector("span");
const observer = new MutationObserver(function () {
console.log("new width", size());
});
observer.observe(div, { subtree: true, childList: true });
function addElem() {
setTimeout(() => {
const newSpan = document.createElement("span");
newSpan.innerHTML = "second";
div.appendChild(newSpan);
console.log("element added");
}, 3000);
}
function size() {
return div.getBoundingClientRect().width;
}
console.log("old width", size());
addElem();
div {
display: inline-block;
border: 1px dashed;
}
span {
background: gold;
}
<div>
<span>one</span>
</div>
You can use something like this:
export function waitElement(elementId) {
return new Promise((resolve, reject) => {
const element = document.getElementById(elementId);
if (element) {
resolve(element);
} else {
let tries = 10;
const interval = setInterval(() => {
const element = document.getElementById(elementId);
if (element) {
clearInterval(interval);
resolve(element);
}
if (tries-- < 0) {
clearInterval(interval);
reject(new Error("Element not found"));
}
}, 100);
}
});
}
I can't get Element.scrollIntoView() to work. I have the code below. There are two locations that it should scroll to, depending on some variable. However, it doesn't scroll to either of them. What am I doing wrong?
class Page extends Component {
scrollToMyRef = (id) => {
var ref = document.getElementById(id);
console.log("Ref1: " + ref); // returns [object HTMLElement]
console.log("Ref2: " + document.ref); // returns undefined
console.log("Id: " + id); // returns myRef
ref.scrollIntoView({
behavior: "smooth",
block: "start",
});
};
componentDidMount() {
if (this.props.location.state) {
if (this.props.location.state.origine) {
this.scrollToMyRef("myRef");
} else {
this.scrollToMyRef("myTopRef");
});
}
}
}
render() {
return (
<div
id="myTopRef"
key="pricing"
className="pricing"
>
...
</div>
<section id=myRef className="section">
...
</section>
...
)
}
After setting a time delay it worked:
scrollToMyRef = (id) => {
var ref = document.getElementById(id);
setTimeout(function () {
ref.scrollIntoView({
behavior: "smooth",
block: "start",
});
}, 100);
};
I was facing the same issue. scrollIntoView was working only if behavior was auto. Setting a delay fixed some cases but not all of them. In addition, Safari doesn't support smooth scrolling at all. I used this polyfill which works for all browsers and all cases.
You can use it in the following way:
import { scrollIntoView } from "seamless-scroll-polyfill";
scrollIntoView(element, {
behavior: "smooth",
block: "start",
},
{
duration: 250 // aprox. the duration that chrome uses,
}
);
I have a chat button created by a script (zendesk chat). It generate an iframe in the page with the ID "launcher".
I'm using Nextjs and Im trying to get this element but I cannot attach a ref since I've not the code.
I am digging the web for a solution and I have tried something like this (in the footer component because it is on all pages and I am able to figure out which page I am on):
React.useLayoutEffect(() => {
function checkElExist() {
if (typeof document !== 'undefined') {
const el = document.querySelector('#launcher');
if (!isSingleVehiclePage) {
console.log('NOT VEHICLE', el);
if (el) {
el.classList.remove('inVehicle');
el.classList.add('notInVehiclePage');
}
} else {
console.log('IN VEHICLE', el);
if (el) {
el.classList.remove('notInVehiclePage');
el.classList.add('inVehicle');
}
}
}
}
window.addEventListener('load', checkElExist);
return () => window.removeEventListener('load', checkElExist);
}, [isSingleVehiclePage]);
I don't know if is the right solution. It kinda works but sometimes the element is null especially when I land on that specific page from an external link (not when I navigate the site).
Is it possibile to get this element? My goal is to add/remove a class to it according to specific pages.
Thanks
EDIT: I dont know if it is relevant, but in the html the iframe is out of my "__next" div. In the pic below, I circled the __next div, the footer div (where my useLayoutEffect code is executed) and the iframe I want to get.
SOLVED:
I use a function that runs recursively every 1000ms. If it can't find the element in 9000ms, it stops.
React.useEffect(() => {
waitForElementToDisplay(
'#launcher',
function (el: any) {
checkElExist(el);
},
1000,
9000
);
function checkElExist(el: any) {
if (!isSingleVehiclePage) {
console.log('NOT VEHICLE', el);
el.classList.remove('inVehicle');
el.classList.add('notInVehiclePage');
} else {
console.log('IN VEHICLE', el);
el.classList.remove('notInVehiclePage');
el.classList.add('inVehicle');
}
}
}, [isSingleVehiclePage]);
function waitForElementToDisplay(
selector: string,
callback: any,
checkFrequencyInMs: number,
timeoutInMs: number
) {
const startTimeInMs = Date.now();
(function loopSearch() {
if (document && document.querySelector(selector) != null) {
console.log('found');
const element = document.querySelector(selector);
callback(element);
return;
} else {
setTimeout(function () {
if (timeoutInMs && Date.now() - startTimeInMs > timeoutInMs) return;
loopSearch();
}, checkFrequencyInMs);
}
})();
}
I'm trying to scroll to a particular card when a button is clicked. I'm using scrollIntoView for that, but its not working.
.js file :
document.getElementById("general_details_edit").addEventListener("click", function(){
this.style.display = "none";
document.getElementById("general_details_card").classList.remove("d-none");
document.getElementById("general_details_card").classList.add("d-block");
console.log("start");
document.getElementById("general_details_card").scrollIntoView({behavior : 'smooth'});
console.log("end")
//not working
All of the other javascript code is working. How can i resolve this?
I get the console messages "start" and "end" as well.
Also, when i run the same line from browser's console, it works.
EDIT :
Vijay's comment about using a timeout worked for me. But how i can wait for the element to load instead of waiting for a specific amount of time?
Current code :
const scroll = (el) => {
setTimeout(function () { el.scrollIntoView( {behavior : 'smooth', block : 'end'}); }, 500);
}
One way would be to change the waiting time to zero milliseconds once the DOM is fully loaded.
var mSeconds = 500;
window.addEventListener('DOMContentLoaded', function(event) {
mSeconds = 0;
});
const scroll = (el) => {
setTimeout(function () {
el.scrollIntoView( {behavior : 'smooth', block : 'end'});
}, mSeconds);
}
I am trying to select some value preferably px of how much I have scrolled down so I can conditionally hide the element.
Something like total height - scrolled height would be ideal
Problem
I'm having trouble selecting the proper property.
console.log doesn't help as it renders the actual body tag then.
Here's the code
const scrollHandler = (event) => {
let scrollTop = event.srcElement.body.offsetHeight;
console.log(scrollTop)
setIsSearchVisible(false)
}
useEffect(() => {
window.addEventListener('scroll', scrollHandler, true);
return () => {
window.removeEventListener('scroll', scrollHandler, true);
}
},[])
Would also appreciate it if someone could point me to the documentation of the same thanks!
I was able to figure it out , Instead of using the event object I simply used the window object, Something like this
const scrollHandler = (event) => {
let scrollTop = window.scrollY;
console.log(scrollTop);
setIsSearchVisible(false);
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler, true);
return () => {
window.removeEventListener("scroll", scrollHandler, true);
};
}, []);