What does the isVisible property mean in intersectionObeserver API? - html

Before lynching me in the comments hear me out.
The intersectionobserver is supposed to listen for when an element is scrolled into view right?
But when I do so the isVisible property is false, cant figure out why. I think its because Im not understanding isVisible property because the other properties are working.
Here is my code:
import React, { useRef, useEffect, useState, useContext } from 'react'
import ReactGA from 'react-ga';
import Context from '../utils/context'
const Home = props => {
const context = useContext(Context)
const [scrollState, setScroll] = useState(false)
const intersectTarget = useRef(null)
useEffect(() => {
// ReactGA.pageview(props.location.pathname);
if(!context.initialLoadProp) {
context.setInitialLoadProp(true)
}
}, [])
useEffect(() => {
const opts = {
root: null,
rootMargin: '0px',
threshold: 0
}
const callback = entry => {
console.log(entry)
}
const observerScroll = new IntersectionObserver(callback, opts)
observerScroll.observe(intersectTarget.current)
}, [])
return(
<div>
<img height="500px"
width="500px"
src="https://timedotcom.files.wordpress.com/2019/03/kitten-report.jpg" alt=""/>
<div id="heading1" style={{height: '500px'}}>
<h1>First Heading</h1>
</div>
<div style={{height: "500px"}}>
<h1 ref={intersectTarget}
id="heading2">
Second Heading
</h1>
</div>
</div>
)
};
export default Home;
My output even thought isIntersecting is true isVisible is false.

To activate IntersectionObserver v2 isVisible feature, you must add two options as follows:
const options = {
root: null,
rootMargin: '0px',
threshold: 0,
/* required options*/
trackVisibility: true,
delay: 100 // minimum 100
}
Visibility is calculated as follows:
If the observer's trackVisibility attribute is false, then the target is considered visible. This corresponds to the current v1 behavior.
If the target has an effective transformation matrix other than a 2D translation or proportional 2D upscaling, then the target is considered invisible.
If the target, or any element in its containing block chain, has an effective opacity other than 1.0, then the target is considered invisible.
If the target, or any element in its containing block chain, has any filters applied, then the target is considered invisible.
If the implementation cannot guarantee that the target is completely unoccluded by other page content, then the target is considered invisible.
Check IntersectionObserver v2 browser support before using it https://caniuse.com/#feat=intersectionobserver-v2

The "isVisible" property is part of proposed Intersection Observer v2 updates concerning actual visibility of the target element to the user. When passing the options object to the observer you can include a "trackVisibility" boolean to implement it, but a corresponding "delay" property is also required. The value of this delay is in milliseconds and needs to be a minimum of 100. If the appropriate delay is not provided then you'll receive an error in the console and the observer will not have been initiated.
At this point I'm only aware of Chrome supporting this feature. So far I've been unable to actually get the visibility feature to work, the delay seems to work though. It's an experimental feature of an experimental feature.
Intersection Observer v2 article

[note: I wrote the specification for IntersectionObserver and implemented it in Chrome].
As stated in previous answers, isVisible only has meaning if the browser supports IntersectionObserver V2 (currently, only chromium-based browsers, including Chrome and Edge); and if you include the 'trackVisibility' and 'delay' parameters when constructing the observer.
This is not considered to be an experimental feature in Chrome; it is a complete, stable feature used by large production websites. However, it is unclear when it will be supported by other browsers. The most up-to-date information about the browsers supporting this feature is here:
https://chromestatus.com/features/5878481493688320
If you think the feature could be improved, consider filing an issue in the spec repository:
https://github.com/w3c/IntersectionObserver/issues

According to https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry there is no isVisible property as far as I can see.
If your goal is to check when the element is completely in view, you can change the threshold to threshold: 1 and then check if the intersectionRatio is equal to one entry.intersectionRatio == 1.
I might have missed the isVisible somewhere but I cant find it.
Hope this helps!

Related

Web component setting property race condition

After a lot of digging I figured out sometimes if I set a property before the element is fully constructed, it overrides the setter. I found this helpful code on google developers:
A developer might attempt to set a property on your element before its definition has been loaded. This is especially true if the developer is using a framework which handles loading components, inserting them into to the page, and binding their properties to a model.
connectedCallback() {
...
this._upgradeProperty('checked');
}
_upgradeProperty(prop) {
if (this.hasOwnProperty(prop)) {
let value = this[prop];
delete this[prop];
this[prop] = value;
}
}
Basically if the setter has been overriden by the property, it deletes the property, and then reapplies it so that the setter picks it up.
It works about 70% of the time. I can get it to work 100% with a nasty hack of setting a timeout:
_upgradeProperty(prop) {
if (this.hasOwnProperty(prop)) {
let value = this[prop];
delete this[prop];
setTimeout( () => this[prop] = value, 1); // <-- Hack here
}
}
This feels really gross, is there any other way to upgrade the property after the element has actually got its getters/setting fully created and ready to go (connectedCallback is not cutting it). Is it a Chrome Bug?

Wrong colors displayed after React renders div to DOM

I was writing some code to map random colors to cells in row.
const COLORS = ['blue', 'green', 'orange', 'red', 'purple', 'yellow'];
const createRandomColors = () => {
const randomColors = [];
for (let i = 0; i < COLORS.length; i++) {
const randomColor = COLORS[Math.floor(Math.random() * COLORS.length)];
randomColors.push(randomColor);
}
return randomColors;
}
const App = () => {
const row = useMemo(createRandomColors, []);
console.log(row);
const cells = useMemo(() => row.map((cell, cellIndex) =>
<div key={cellIndex} style={{ backgroundColor: cell }}>{cellIndex}</div>
), [row]);
cells.forEach(cell => console.log(cell.props.style.backgroundColor));
return (
<div className="app">
<div className="row">
{cells}
</div>
</div>
);
};
export default App;
The problem is that after rendering div elements they have completely different inline style background-color that was specified when mapping divs.
Please see CodeSandBox and take a look at console log and real results rendered.
Why is this happening?
The reason this appears wonky is related to using <StrictMode> in your index.html file. If you remove <StrictMode> you'll see your code works the way you expect. But don't do that, you really do have a bug.
React wants functional components to be idempotent, meaning they do not have side effects. To help you catch side-effects, React will call your code twice back to back when it renders. see strict mode By doing this, it helps uncover subtle issues like the one you're currently experiencing.
One solution is to create the random colors once using useEffect(). Another is to generate the colors outside the functional component.
UPDATE
Please mark the answer as 'accepted' if it solves your issue. You are correct. useMemo will save the computation so it will not be re-computed unless dependencies change. However, react is purposely calling your code twice (in debug mode only) to help you catch unintentional side effects in your classes or hooks. When using strict mode, it's as if you have two of the component instead of one. i.e.
/* StrictMode in debug */
<StrictMode>
<App/>
</StrictMode>
/* ... be like this: */
<>
<App/>
<App/>
</>
If you (temporarily) remove the <StrictMode> tag you'll see your code works as expected. And if you add code that causes your component to render again (e.g. a click counter) your useMemo should prevent the cells from being regenerated each render.
Add a console log to print every time createRandomColors() is called. Since your code is being called twice, you should see the debug log appear twice, but you don't. Why not? React surpasses the console.log the 2nd time it calls your code.
At the top of your code (line 3) add const log = console.log, then replace everywhere you use console.log with just log and you'll have the full picture of what's occurring.
Keep experimenting. We've all been here.

Prevent zoom in Forge viewer when clicking in Model Browser

There has been a change in the click behavior in the model browser from version 2 to version 3 of the Forge Viewer. In v2, a single click would select the elements and a double click would zoom to the selected elements. In v3, a single click will zoom to the elements. Sometimes this is great, but often it would be nice to disable this behavior. Is there an easy way to do this today? And if not, could it be possible to add a disableZoomOnSelection function to the viewer API?
I know that the eyes in the browser will take care of the show and hide elements, but it’s very easy to klick in the three by accident and suddenly the viewer zoom without the user intention.
Regards
Frode
I dig that code for you looking at the implementation of the ViewerModelStructurePanel that I was exposing in that article: Supporting multiple models in the new ModelStructurePanel
Events that occur in the tree are mapped to predefined actions through the options.docStructureConfig object, so the workaround is to instantiate a new ViewerModelStructurePanel with the desired options:
viewer.addEventListener(Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, () => {
var options = {
docStructureConfig: {
// go with default, which is viewer.select(node)
// click: {
// onObject: ["toggleOverlayedSelection"]
//},
clickShift: {
onObject: ["toggleMultipleOverlayedSelection"]
},
clickCtrl: {
onObject: ["toggleMultipleOverlayedSelection"]
}
}
}
var customModelStructurePanel =
new Autodesk.Viewing.Extensions.ViewerModelStructurePanel(
viewer, 'Browser', options)
viewer.setModelStructurePanel(customModelStructurePanel)
})
The double-click however is not linked to an event in the current implementation, so for a more powerful customization I would recommend you replace the whole implementation by a custom one as exposed in the article and implement desired action handlers. I implemented it as drop-in replacement, so in that case you just need to include it to your html after the viewer script and don't have to replace the model browser in OBJECT_TREE_CREATED_EVENT
The model browser receives an options object in its constructor. There, you can specify the actions for different events (click, clickCtrl, clickShift, etc).
To set the old behavior you can try the following:
var options = {};
options.docStructureConfig = {
"click": {
"onObject": ["isolate"]
},
"clickCtrl": {
"onObject": ["toggleVisibility"]
}
};
NOP_VIEWER.setModelStructurePanel(new ave.ViewerModelStructurePanel(NOP_VIEWER, "", options));
NOP_VIEWER can be replaced with your own viewer variable.

Is there a way to detect reflows instantly in Angular, without using $timeout?

I'm working on a site with a scrollable list of canvases for plotting data, and I need to resize the canvases whenever the width of the div they're in changes.
I have it working in most cases, but if I delete a plot such that the scroll bar goes away, it doesn't work. I tried the following, where plotsScroller is the div with the scroll bar and plotsList is what's inside of it:
$scope.isScrollingPlotsList = function() {
return plotsList.offsetHeight > plotsScroller.offsetHeight;
}
$scope.$watch('isScrollingPlotsList()', $scope.$apply);
This code would work except that no $digest happens after the reflow that removes the scroll bar; $digest is called when I delete a plot but I guess the reflow happens later.
Does anyone know how I can detect the reflow without using $timeout?
You can use Mutation Observers to detect changes in the DOM. When a change occur you will be notified and can traverse to see what changed.
An example of usage (source):
// select the target node
var target = document.querySelector('#some-id');
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type);
});
});
// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true };
// pass in the target node, as well as the observer options
observer.observe(target, config);
// later, you can stop observing
observer.disconnect();
Also see the source link for details.
It should be supported in all major browsers incl. IE11. For [IE9, IE11> there exists a polyfill that can be used.
The specification

2-way data binding in native web components

I've been reading up on web components and am pretty intrigued by the nascent spec. Does anyone know if there is any support for 2-way data binding in the DOM, without having to use Polymer? An example would be appreciated.
Object.observe is a potential new way to do databinding in javascript. This feature is scheduled for Ecmascript 7(javascript), but some browsers currently support it, check here. Also check out this html5rocks article on object.observe
No, data binding isn't part of the Web Components spec.
You can of course implement data binding yourself using native JavaScript event listeners, and possibly the Proxy object, but it's probably best not to re-invent the wheel: if you want data binding, choose one of the many JavaScript frameworks out there which supports that. Polymer, React, Angular, and Vue are some recent examples of such libraries.
I've been playing around with this over the last few days. You can create a StateObserver class, and extend your web components from that. A minimal implementation looks something like this:
// create a base class to handle state
class StateObserver extends HTMLElement {
constructor () {
super()
StateObserver.instances.push(this)
}
stateUpdate (update) {
StateObserver.lastState = StateObserver.state
StateObserver.state = update
StateObserver.instances.forEach((i) => {
if (!i.onStateUpdate) return
i.onStateUpdate(update, StateObserver.lastState)
})
}
}
StateObserver.instances = []
StateObserver.state = {}
StateObserver.lastState = {}
// create a web component which will react to state changes
class CustomReactive extends StateObserver {
onStateUpdate (state, lastState) {
if (state.someProp === lastState.someProp) return
this.innerHTML = `input is: ${state.someProp}`
}
}
customElements.define('custom-reactive', CustomReactive)
class CustomObserved extends StateObserver {
connectedCallback () {
this.querySelector('input').addEventListener('input', (e) => {
this.stateUpdate({ someProp: e.target.value })
})
}
}
customElements.define('custom-observed', CustomObserved)
<custom-observed>
<input>
</custom-observed>
<br />
<custom-reactive></custom-reactive>
fiddle here
I like this approach because it occurs directly between precisely those elements you want to communicate with, no dom traversal to find data- properties or whatever.
One way: $0.model = {data}; setter on $0 assigns $0.data, responding to the update, and the other way: $1.dispatchEvent(new CustomEvent('example', {detail: $1.data, cancelable: true, composed: true, bubbles: true})); with $0.addEventListenever('example', handler) gives 2 way data binding. The data object is the same, shared on 2 elements, events and setters allow responding to updates. To intercept updates to an object a Proxy works model = new Proxy(data, {set: function(data, key, value){ data[key] = value; ...respond... return true; }}) or other techniques. This addresses simple scenarios. You might also consider looking at and reading the source for Redux, it provides conventions that seem relatively popular. As Ajedi32 mentions reinventing the wheel for more complex scenarios is not so practical, unless it's an academic interest.