Displaying Go To Top button when page becomes scrollable - javascript

Just wondering How I can do this in Angular 2/4 : This might be easy but I just can't figure out.
Here is my code:
Let me explain it, I have a component which scrolls me to the top of the page, when I am at the bottom. But the floating div i.e, little red arrow always stays visible even when page need not scroll.
In Html:
Each button is dynamically linked to div. So div displays when button is clicked
<div *ngFor="let sampledata of SAMPLEDATA; trackBy: trackId">
<button (click)="transmitInfo(sampledata ,0)" > </button>
<div *ngFor="let data of sampledata .data; trackBy: trackId" >
<button (click)="transmitInfo(data,1)" > </button>
</div>
<!-- This keeps on going -->
</div>
<div>
<div *ngIf="renderdata === 0"> {{Object Data}}</div>
<div *ngIf="renderdata === 1">{{Object Data}}</div>
<div *ngIf="renderdata === 2">{{Object Data}}</div>
</div>
<div id="scroolUpRight">
<img src="../../../content/images/scrollup.png" width="50px" height="50px" (click)="scrollToTop()">
</div>
Let's assume when a user clicks on button 2 or 3, 2nd or 3rd div is displayed based on button clicked, this div's are a huge data. Page automatically becomes scrollable when these are activated.
In Css:
#scroolUpRight {
position: fixed;
bottom: 4%;
right: 2%;
}
#scroolUpRight :hover {
cursor: pointer;
}
In my Component I have this to take me to the top of the page:
ngOnInit() {
this.renderdata = 0;
}
transmitInfo(data, type): void {
if (type === 1) { this.sampleData = data; this.renderdata = 1; }
if (type === 2) { this.dataData = data; this. renderdata = 2; }
}
scrollToTop() {
return window.scrollTo(0, 0);
}
Now I don't know if this works but I did this:
toogleScroolButton(): void {
if (window.screenY > 300 ) {
console.log('window length is 300 +');
}
}
But this is a function. How can I make a function or component that auto detects when page becomes scrollable and display this div, hide it when not scrollable.
Expected Result : Is to make this div visible once person starts to scroll.
Previous Knowledge:
I used Javascript and Jquery before to do the same. But how do I use
angular2,4 or higher for this? Reason I need this is to animate this div when
person starts to scroll.
I do accept recommendations to optimize the above code. Please do let me know if any.. ;)

This Worked. I need to get HostListener to get windows scroll even to see if I can scroll the page.
window.scrollY gives me the scroll page size which helps me in finding out if I am scrolling my page. If scrollY reaches to certain count I can say I am scrolling down i.e, I can trigger an *ngIf to true if I am scrolling bottom else I can make it false. Code Below :)
Add
import { HostListener } from '#angular/core';
export class BlaBlaBla {
//And this did the trick
activateGoTop : boolean;
OnNgInit :: activateGoTop = false /* added Silly Reference please put this in ngOnInit() { --- }*/
#HostListener('window:scroll',[])
onWindowScroll() {
if ( window.scrollY > 100 ) {
this.activateGoTop = true;
} else {
this.activateGoTop = false;
}
}
}
in Html:
//Gets activated when screenY is scrolled for more than 100px
<div id="scroolUpRight" *ngIf="activateGoTop">
<img src="../../../content/images/scrollup.png" width="50px" height="50px" (click)="scrollToTop()">
</div>
Hope this helps someOne .. ;)

You can use a simple *ngIf binding with your method:
<div *ngIf="scrollButton()">
Top <button>up button</button>
</div>
with scrollButton() method simple as that:
public scrollButton():boolean {
return window.screenY > 300;
}
The div will only get rendered if scrollButton() method returns true, this allows you to customize your top button render conditions easily, because you only need to return a boolean from it.

Related

Vue js dynamically show/hide div

I have two divs:
<div class = "bigger">
</div>
<div class = "smaller">
</div>
I want to hide the bigger and show the smaller div if screen width < 710 and
vice versa. I'm able to trigger resize event by:
mounted() {
window.addEventListener("resize", this.displayWindowSize)
})
},
methods () {
displayWindowSize(event) {
if(document.documentElement.clientWidth > 710){
//hide smaller div
console.log("bigger");
}else{
//hide bigger div
console.log("smaller");
}
}
}
But I couldn't find in the internet how I can hide div in vuejs.
You guys have an idea?
Edit: I want to trigger this function to hide the div whenever screenwidth changes not only once.
It depends if you want to hide it remove it, if you want to remove you can do something like
methods () {
isBigger() {
return (document.documentElement.clientWidth > 710);
}
}
and your template
<div v-if="isBigger" class="bigger">
</div>
<div v-else class="smaller">
</div>
or
<div v-show="isBigger()" class="bigger">
</div>
<div v-show="!isBigger()" class="smaller">
</div>
if you want to decide which one to use then check this question out
What is better in vue.js 2, use v-if or v-show?
thx #top talent for answering fastly. Your solution didn't work for me but I slightly changed it:
if(document.documentElement.clientWidth > 710){
document.getElementsByClassName[0].style.display = "none";
document.getElementsByClassName("bigger")[0].style.display = "block";
}else{
document.getElementsByClassName("smaller")[0].style.display = "block";
document.getElementsByClassName("bigger")[0].style.display = "none";
}
workes perfect
This will be correct method.
methods () {
displayWindowSize(event) {
if(document.documentElement.clientWidth > 710){
document.querySeletor(".smaller").style.display = "none"
document.querySeletor(".bigger").style.display = "block"
console.log("bigger");
}else{
document.querySeletor(".smaller").style.display = "block"
document.querySeletor(".bigger").style.display = "none"
console.log("smaller);
}
}
}

Close modal in vanilla js [duplicate]

I'd like to detect a click inside or outside a div area. The tricky part is that the div will contain other elements and if one of the elements inside the div is clicked, it should be considered a click inside, the same way if an element from outside the div is clicked, it should be considered an outside click.
I've been researching a lot but all I could find were examples in jquery and I need pure javascript.
Any suggestion will be appreciated.
It depends on the individual use case but it sounds like in this example there are likely to be other nested elements inside the main div e.g. more divs, lists etc. Using Node.contains would be a useful way to check whether the target element is within the div that is being checked.
window.addEventListener('click', function(e){
if (document.getElementById('clickbox').contains(e.target)){
// Clicked in box
} else{
// Clicked outside the box
}
});
An example that has a nested list inside is here.
You can check if the clicked Element is the div you want to check or not:
document.getElementById('outer-container').onclick = function(e) {
if(e.target != document.getElementById('content-area')) {
console.log('You clicked outside');
} else {
console.log('You clicked inside');
}
}
Referring to Here.
you can apply if check for that inside your click event
if(event.target.parentElement.id == 'yourID')
In Angular 6 and IONIC 3, I do same as here:
import {Component} from 'angular/core';
#Component({
selector: 'my-app',
template: `
<ion-content padding (click)="onClick($event)">
<div id="warning-container">
</div>
</ion-content>
`
})
export class AppComponent {
onClick(event) {
var target = event.target || event.srcElement || event.currentTarget;
if (document.getElementById('warning-container').contains(target)){
// Clicked in box
} else{
// Clicked outside the box
}
}
}
This working fine on web/android/ios.
It might be helpful for someone, Thanks.
Try this solution it uses pure javascript and it solves your problem. I added css just for better overview... but it is not needed.
document.getElementById('outer-div').addEventListener('click', function(){
alert('clicked outer div...');
});
document.getElementById('inner-div').addEventListener('click', function(e){
e.stopPropagation()
alert('clicked inner div...');
});
#outer-div{
width: 200px;
height: 200px;
position: relative;
background: black;
}
#inner-div{
top: 50px;
left: 50px;
width: 100px;
height: 100px;
position: absolute;
background: red;
}
<div id="outer-div">
<div id="inner-div">
</div>
</div>
I came up with a hack for this that's working well for me and that might help others.
When I pop up my dialog DIV, I simultaneously display another transparent DIV just behind it, covering the whole screen.
This invisible background DIV closes the dialog DIV onClick.
This is pretty straightforward, so I'm not going to bother with the code here. LMK in the comments if you want to see it and I'll add it in.
HTH!
closePopover () {
var windowBody = window
var popover = document.getElementById('popover-wrapper') as HTMLDivElement;
windowBody?.addEventListener('click', function(event){
if(popover === event.target) {
console.log("clicked on the div")
}
if(popover !== event.target) {
console.log("clicked outside the div")
}
})
}
}
I recently needed a simple vanilla JS solution which solves for:
Ignoring specific selectors including whether a parent contains one of these selectors
Ignoring specific DOM nodes
This solution has worked quite well in my app.
const isClickedOutsideElement = ({ clickEvent, elToCheckOutside, ignoreElems = [], ignoreSelectors = [] }) => {
const clickedEl = clickEvent.srcElement;
const didClickOnIgnoredEl = ignoreElems.filter(el => el).some(element => element.contains(clickedEl) || element.isEqualNode(clickedEl));
const didClickOnIgnoredSelector = ignoreSelectors.length ? ignoreSelectors.map(selector => clickedEl.closest(selector)).reduce((curr, accumulator) => curr && accumulator, true) : false;
if (
isDOMElement(elToCheckOutside) &&
!elToCheckOutside.contains(clickedEl) &&
!didClickOnIgnoredEl &&
!didClickOnIgnoredSelector
){
return true;
}
return false;
}
const isDOMElement = (element) => {
return element instanceof Element || element instanceof HTMLDocument;
}
In React you can use useClickOutside hook from react-cool-onclickoutside.
Demo from Github:
import { useClickOutside } from 'use-events';
const Example = () => {
const ref1 = React.useRef(null);
const ref2 = React.useRef(null);
const [isActive] = useClickOutside([ref1, ref2], event => console.log(event));
return (
<div>
<div ref={ref1} style={{ border: '1px dotted black' }}>
You are {isActive ? 'clicking' : 'not clicking'} outside of this div
</div>
<br />
<div ref={ref2} style={{ border: '1px dotted black' }}>
You are {isActive ? 'clicking' : 'not clicking'} outside of this div
</div>
</div>
);
};
Live demo

How to display a header only after the user starts to scroll down? [React]

I'm using react, and I would like to display a header only after the user has scrolled down 150px on the page.
So when the user starts to scroll down, I would like a sticky header to appear. And again, when the user scrolls to the top of the page, the sticky header disappears.
<div className="container">
// display this on top of the page:
<JumbotronImage />
// display this when user starts to scroll, sticky on top of the page
<StickyHeader />
</div>
I tried to do it with window.addEventListener('scroll'... , and I also tried https://github.com/fisshy/react-scroll but couldn't get it to work yet. Any suggestions?
I can think upon that your code would look as following. And the solution should fit like this.
export class App extends React.Component {
componentDidMount() {
ReactDOM.findDOMNode(this.stickyHeader).addEventListener('scroll', this.handleScroll.bind(this));
}
componentWillUnmount() {
ReactDOM.findDOMNode(this.stickyHeader).removeEventListener('scroll', this.handleScroll.bind(this));
}
handleScroll() {
// Add the logic here
}
render() {
const {props} = this;
return (
<div className="container">
// display this on top of the page:
<JumbotronImage />
// display this when user starts to scroll, sticky on top of the page
<StickyHeader ref = {ele => this.stickyHeader = ele} />
</div>
);
}
}
You can refer this article which worked for me, http://blog.sodhanalibrary.com/2016/07/detect-scroll-direction-using-reactjs.html#.Wo0hq0nTS-4 to add logic inside handleScroll.
I generally use this script for sticky header for my header with id="sticky"
$(window).scroll(function () {
var iCurScrollPos = $(this).scrollTop();
if (iCurScrollPos) {
//#sticky put your element for which you want it to apply
$('#sticky').addClass('stickyMenu');
} else {
$('#sticky').removeClass('stickyMenu');
}
});
Add css for attached class

Tabindex not working in Chrome (React app)

I use react and react-modal to create an overlay over a website. This overlay contains various elements and also a form (overview below). I want to be able to guide the user through the form using TAB keys. I assigned tabindex=0 to the required elements to be tabbable in order of appearance.
My problem is: It does not work in Chrome (Version 61.0.3163.100) while it does work in Firefox. I read that this happens if any element up the DOM-tree is invisible or has height/width of 0. I made some styling changes to fix that but with no effect.
<div class="ReactModalPortal">
<div data-reactroot="" class="ReactModal__Overlay" style="position: fixed; top: 0px; left: 0px; right: 0px; bottom: 0px;">
<div class="ReactModal__Content" tabindex="-1" aria-label="Questionnaire" style="position: absolute; top: 0px; left: 0px; right: 0px; height: 100%; background: transparent none repeat scroll 0% 0%; overflow: auto;">
<!-- Some other stuff and nested elements -->
<div id="...">
<form>
<input tabindex="0">
<button tabindex="0">
</form>
</div>
</div>
</div>
As you can see one of the parent elements has tabindex="-1". When changing it through the inspect function in Chrome or programmatically with JS the problem still persists (or is it a difference if the element was rendered with this index initially?).
Update
I realized that something else was causing the issues. I was using the CSS attribute initial: all on the root node of my modal to fence my inner CSS from everything outside. For some reason this was preventing the tabindex from working. If you can help me understanding I will reward this wis the bounty. My workaround is just not using all: initial (it is not IE-compatible anyways but also there is no real good alternative I am aware of).
all: initial resets all CSS properties of the node with initial properties.
For display property, the initial value would be inline.
So, setting all: initial to the root div would set the display property to inline. An inline element does not have height or width, so these are 0x0.
This is also because the div contains only fixed, absolutely positioned elements.
React Modal checks if elements are focusable by running a loop through all the elements inside the modal. However, for an element to be focusable, it has to visible. For each element, we have to iterate till the body element to ensure it's visibility.
Here is the function that checks whether the element is visible.
function hidden(el) {
return (
(el.offsetWidth <= 0 && el.offsetHeight <= 0) || el.style.display === "none"
);
}
As you can see, our div would have no offsetHeight or offsetWidth and would be deemed as hidden. Therefore, the modal cannot not be focused.
I had the same issue and was not able to get other solutions working quicky, so I came up with brute force approach. Make a ref to the container element that holds the focusable elements that you wish to make tabbable.
const formRef = useRef();
<ReactModalTabbing containerRef={formRef}>
<form ref={formRef} onSubmit={handleSubmit} >
<input type="text" />
<input type="text" />
<input type="text" />
<input type="text" />
</form>
</ReactModalTabbing>
And this is the component
import React, { useState, useEffect } from 'react';
const ReactModalTabbing = ({ containerRef, children }) => {
const [configuredTabIndexes, setConfiguredTabIndexes] = useState(false);
const focusableElements = () => {
// found this method body here.
//https://zellwk.com/blog/keyboard-focusable-elements/
return [...containerRef?.current?.querySelectorAll(
'a, button, input, textarea, select, details, [tabindex]:not([tabindex="-1"]):not([type="hidden"]):not([disabled])'
)];
}
const isTabbable = (element) =>{
if(element.getAttribute('tabindex')){
return true;
}
return false;
}
const findElementByTabIndex = (tabIndex) => {
return containerRef?.current?.querySelector(`[tabindex="${tabIndex}"]`);
}
const moveFocusToTabIndex = (tabIndex) => {
findElementByTabIndex(tabIndex)?.focus();
}
const handleKeyDownEvent = (event) => {
if(!isTabbable(event.target)){
return;
}
const tabIndex = parseInt(event.target.getAttribute('tabindex'));
if(event.shiftKey && event.key === 'Tab'){
moveFocusToTabIndex(tabIndex - 1);
}else if(event.key === 'Tab'){ //should probably make sure there is no other modifier key pressed.
moveFocusToTabIndex(tabIndex + 1);
}
}
useEffect(() => {
if(!configuredTabIndexes && containerRef.current){
setConfiguredTabIndexes(true);
focusableElements().forEach((el, index) => el.setAttribute('tabindex', index + 1));
containerRef?.current?.addEventListener('keydown', handleKeyDownEvent);
}
});
return children;
}
export default ReactModalTabbing;

Replace an img src on scroll

I am trying to replace logo-text-black src attribute so that the svg img changes as the user scrolls. Is it possible to add this to my current script?
img/logo-text-white.svg // Top State
img/logo-text-black.svg // Scroll State
HTML
<nav class="navbar navbar-default navbar-fixed-top">
<div class="navbar-header">
<img class="logo" src="img/logo.svg">
<a href="#top"><img class="logo-text" src="img/logo-text-white.svg">
</a>
</div>
</nav>
JS
$(window).scroll(function() {
var value = $(this).scrollTop();
if (value > 100)
$(".navbar-default").css("background", "white"); // Scroll State
else
$(".navbar-default").css("background", "transparent"); // Top state
});
To replace image source you may use jQuery .attr method:
var initialSrc = "img/logo.svg";
var scrollSrc = "img/logo-text-black.svg";
$(window).scroll(function() {
var value = $(this).scrollTop();
if (value > 100)
$(".logo").attr("src", scrollSrc);
else
$(".logo").attr("src", initialSrc);
});
This approach requires only one <img> with logo class in the HTML:
<nav class="navbar navbar-default navbar-fixed-top">
<div class="navbar-header">
<img class="logo" src="img/logo.svg">
</div>
</nav>
Ignoring the fact that the simple answer to the question asked is that you use the .attr function to change an attribute for an element when using jQuery, this is how I would go about accomplishing the task set forth in your question.
First, I would put all of this in a function (mainly to separate the variables and logic from other page scripts to prevent interference).
My next bit of advice would be to implement the background color change in two or more CSS classes. This has the benefit of simplifying the JavaScript, as well as keeping the styling part in the styling area.
Next, I like to make constant variables for my "magic words", so that if I change the word used later on I only have to change the word once in the code, instead of everywhere the word is used.
// cache the magic words
const DARK = 'dark';
const LIGHT = 'light';
I would put the image sources into an object where the keys are the magic words associated with those sources. This allows for quick and convenient lookup later.
// define our different sources for easy access later
const sources = {
light: "http://via.placeholder.com/150x50/fff/000?text=logo",
dark: "http://via.placeholder.com/150x50/000/fff?text=logo"
};
After that I would pre-load the images to prevent a visual delay the first time the source is changed.
// pre-load the images to prevent jank
document.body.insertAdjacentHTML('beforeend', `
<div style="display: none!important">
<img src="${ sources[LIGHT] }">
<img src="${ sources[DARK] }">
</div>
`);
It is important to note that performing tasks on-scroll can cause problems.
The main problems are:
The effects can be blocking, which means that process heavy tasks will cause "scroll jank". This is where there is a visual inconsistency with how the page scrolls.
It is possible for the scroll event to fire while there is already a scroll event listener executing. This may cause the two executions to interfere with each other.
Combatting these problems is easy:
To prevent scroll-jank, wrap the handler in a setTimeout call. This will move the execution of the handler to the top of the stack to be executed at the next earliest convenience.
To prevent multiple handlers from running simultaneously, define a "state" variable outside of the handler to keep track of execution state.
This variable will be set to true when an event handler is executing and false when there is no event handler execution. When the handler execution begins, check the value of the state variable:
If it is true, cancel the execution of this handler call.
If it is false, set the state to true and continue.
Just make sure that wherever you may be exiting the function, you also reset the state variable.
// define our scroll handler
const scroll_handler = _ => setTimeout(_ => {
// if we are already handling a scroll event, we don't want to handle this one.
if (scrolling) return;
scrolling = true;
// determine which theme should be shown based on scroll position
const new_theme = document.documentElement.scrollTop > 100 ? DARK : LIGHT;
// if the current theme is the theme that should be shown, cancel execution
if (new_theme === theme) {
scrolling = false;
return;
}
// change the values
logo.src = sources[new_theme];
el.classList.remove(theme);
el.classList.add(new_theme);
// update the state variables with the current state
theme = new_theme;
scrolling = false;
});
After that, just assign the event listener.
Here it is all together:
function navbarSwitcher(el) {
// cache the reference to the logo element for use later
const logo = el.querySelector('.logo');
// cache the magic words
const DARK = 'dark';
const LIGHT = 'light'
// define our state variables
let scrolling = false;
let theme = LIGHT;
// define our different sources for easy access later
const sources = {
light: "http://via.placeholder.com/150x50/fff/000?text=logo",
dark: "http://via.placeholder.com/150x50/000/fff?text=logo"
};
// pre-load the images to prevent jank
document.body.insertAdjacentHTML('beforeend', `
<div style="display: none!important">
<img src="${ sources[LIGHT] }">
<img src="${ sources[DARK] }">
</div>
`);
// define our scroll handler
const scroll_handler = _ => setTimeout(_ => {
// if we are already handling a scroll event, we don't want to handle this one.
if (scrolling) return;
scrolling = true;
// determine which theme should be shown based on scroll position
const new_theme = document.documentElement.scrollTop > 100 ? DARK : LIGHT;
// if the current theme is the theme that should be shown, cancel execution
if (new_theme === theme) {
scrolling = false;
return;
}
// change the values
logo.src = sources[new_theme];
el.classList.remove(theme);
el.classList.add(new_theme);
// update the state variables with the current state
theme = new_theme;
scrolling = false;
});
// assign the event listener to the window
window.addEventListener('scroll', scroll_handler);
}
// attach our new plugin to the element
navbarSwitcher(document.querySelector('.wrap'));
body {
height: 200vh;
}
.wrap {
width: 100%;
position: fixed;
}
.wrap.light {
background-color: white;
}
.wrap.dark {
background-color: black;
}
<div class="wrap light">
<img class="logo" src="http://via.placeholder.com/150x50/fff/000?text=logo">
</div>

Categories

Resources