Let assume that I have a lot of html elements need to use MDCMenu. I don't want to init them one by one, so I init all of them with the code below:
html:
<button class="my-menu-toggle" data-toggle="mdc-menu" data-target="#my-menu">Menu Toggle</button>
<div class="mdc-menu" id="my-menu">
</div>
js:
document.querySelectorAll('[data-toggle="mdc-menu"]').forEach(toggleEl => {
let menuEl = document.querySelector(toggleEl.dataset.target);
let menu = new MDCMenu(menuEl);
toggleEl.addEventListener('click', (e) => {
menu.open = !menu.open;
});
// maybe I should do this, just wondering that if MDC already do same thing that I haven't figure out.
menuEl.MDCMenu = menu;
});
then I want to do somethings with one of menu, how can I get the MDCMenu instance of the element?
I created a react component that is made of a 3d cube (using css) and 2 buttons that rotate this cube 90 degrees (1 button rotates the cube left and the other one right)
import React from 'react';
import './App.css';
function App() {
const rotateLeft = async (event) => {
event.preventDefault();
const boxArea = document.getElementsByClassName("box-area")[0];
const newNode = boxArea.cloneNode(true);
boxArea.classList.add('rotateLeft');
setTimeout(function(){
boxArea.parentNode.replaceChild(newNode, boxArea);
}, 3005)
};
const rotateRight = async (event) => {
event.preventDefault();
const boxArea = document.getElementsByClassName("box-area")[0];
const newNode = boxArea.cloneNode(true);
boxArea.classList.add('rotateRight');
setTimeout(function(){
boxArea.parentNode.replaceChild(newNode, boxArea);
}, 3005)
};
return (
<div className="App">
<div className="hexagon-panel">
<div className="wrapper">
<div className="box-area">
<div className="box front"/>
<div className="box back"/>
<div className="box left"/>
<div className="box right"/>
</div>
</div>
<div className="hexagon-actions">
<button onClick={rotateLeft}>Left</button>
<button onClick={rotateRight}>Right</button>
</div>
</div>
</div>
);
}
export default App;
As you can see, I trigger the animation by adding the class, I wait a bit more than 3 seconds (the animation lasts 3 seconds) and then I replace the Node with one that doesn't contain the class.
A better solution (but I don't like it either) is to remove the class after the animation ends.
const rotateLeft = async (event) => {
event.preventDefault();
const boxArea = document.getElementsByClassName("box-area")[0];
boxArea.classList.add('rotateLeft');
setTimeout(function(){
boxArea.classList.remove('rotateLeft');
}, 4000)
};
But I am not convinced. The reason for this is the setTimeout since it is a global function I am afraid of a memory leak (even if it is a small one). Calling a new setTimeout on every click and not erasing the previous one.
I was thinking about this
const rotateLeft = async (event) => {
event.preventDefault();
const boxArea = document.getElementsByClassName("box-area")[0];
boxArea.classList.add('rotateLeft');
const func = setTimeout(function(){
boxArea.classList.remove('rotateLeft');
}, 4000)
clearTimeout(func);
};
But I don't know if that even makes sense.
Is there a way a better way to remove the class without the use of setTimeout? Or in the case that it is needed, what would be a good practice to delete it?
Thank you for your time!
You might be looking for this: Using Animation Events. It includes the animationend event that fires when the animation ends.
Or in the future, you could use the Web Animations API once it is widely implemented.
Or if you want to stick with setTimeout, just put the cleanup code (clearTimeout, remove class, etc.) before starting the animation. This way, you always get a 'clean' state to work with.
I'm trying to remove all jQuery from my code. Until now I used
if ($(selector).find(':focus').length === 0) {
// focus is outside of my element
} else {
// focus is inside my element
}
to distinguish wether the focus is inside of one of my elements. Can you show me a jQuery-free way of doing it?
You can use Node.contains native DOM method for this.
el.contains(document.activeElement);
will check if activeElement is a descendant of el.
If you have multiple elements to check, you can use a some function to iterate.
It is possible with Element's matches() method and with a simple selector string as follows:
let hasFocused = elem.matches(':focus-within:not(:focus)');
let focusedOrHasFocused = elem.matches(':focus-within');
Use CSS :focus pseudo-class in querySelectorAll()
setTimeout(function(){
if (document.querySelectorAll("div :focus").length === 0)
console.log("not focused");
else
console.log("focused")
}, 2000);
<div>
<input type="text">
</div>
Depending on your situation, using events might be more performant.
You can use the focusin and focusout events in that case.
const el = document.getElemen
el.addEventListener("focusin", () => console.log("focus!"));
el.addEventListener("focusout", () => console.log("blur!"));
Note that during focusout events the document.activeElement will be the document body. To work around this issue, you can make use of FocusEvent.relatedTarget.
If you have issue where document.activeElement is returning <body> element after blur event, you just need to wrap it with setTimeout() and it will return correct element.
handleBlur() {
setTimeout(() => {
console.log(document.activeElement); // this actually return active/focused element
});
}
if you are using it standalone without timeout
handleBlur() {
console.log(document.activeElement); // this is returning <body> element
}
Combined some of answers posted here. Using a combination of focusin, focusout, contains and relatedTarget, you should be able to know when focus is on the children of a particular element.
const elm = document.getElementById('check-focus-here')
elm.addEventListener('focusin', (event) => {
console.log(event.target, event.relatedTarget)
// console.log(elm.contains(event.relatedTarget))
})
elm.addEventListener('focusout', (event) => {
console.log(event.target, event.relatedTarget)
console.log(elm.contains(event.relatedTarget))
})
#check-focus-here {
border: 2px solid;
padding: 8px;
margin: 8px;
}
<div id="check-focus-here">
<input id="first-name" type="text" />
<input id="middle-name" type="text" />
<input id="last-name" type="text" />
<button type="button">Save Name</button>
</div>
<button type="button">Tab to this for outside focus</button>
Here's a working example following #Northern and #Adam Šipický answers...
const tr = document.querySelector("table tbody tr");
tr.addEventListener('blur', () => {
setTimeout(() => {
if (!tr.contains(document.activeElement)) {
// Execute your condition code here...
}
}, 200);
}, true);
In 2021 you can probably avoid javascript altogether to check if an element or any of the element's child nodes have focus – unless you are manipulating DOM elements outside of a parent element.
For example:
<div class="parent">
<button>foo</button>
<button>food</button>
<button>foosh</button>
</div>
.parent { background: white }
.parent:focus-within { background: red }
.parent:focus-within button:not(:focus) { opacity: .5 }
None of these existing non CSS based solutions account for the situation where the JavaScript context does not match the frame the node was rendered in. To account for this you would want to do something like the following:
el.contains(el.ownerDocument.activeElement)
To retrieve the selected element you can use:
let activeElement = document.activeElement
To check a specific element:
let elem = document.getElementById('someId');
let isFocused = (document.activeElement === elem);
I have a Vue.js component with several elements in it. I want to automatically scroll to the bottom of that element when a method in the component is called.
Basically, do the same as this. However, I haven't found a way to get the element within my component and modify scrollTop
I'm currently using Vue.js 2.0.8.
2022 easy, readable, smooth scrolling ability, & won't hurt your brain... use el.scrollIntoView()
scrollIntoView() has options you can pass it like scrollIntoView({behavior: 'smooth'}) to get smooth scrolling out of the box and does not require any external libraries.
Here is a fiddle.
methods: {
scrollToElement() {
const el = this.$refs.scrollToMe;
if (el) {
// Use el.scrollIntoView() to instantly scroll to the element
el.scrollIntoView({behavior: 'smooth'});
}
}
}
Then if you wanted to scroll to this element on page load you could call this method like this:
mounted() {
this.scrollToElement();
}
Else if you wanted to scroll to it on a button click or some other action you could call it the same way:
<button #click="scrollToElement">scroll to me</button>
The scroll works all the way down to IE 8. The smooth scroll effect does not work out of the box in IE or Safari. If needed there is a polyfill available for this here as #mostafaznv mentioned in the comments.
As I understand, the desired effect you want is to scroll to the end of a list (or scrollable div) when something happens (e.g.: an item is added to the list). If so, you can scroll to the end of a container element (or even the page it self) using only pure JavaScript and the VueJS selectors.
var container = this.$el.querySelector("#container");
container.scrollTop = container.scrollHeight;
I've provided a working example in this fiddle.
Every time a item is added to the list, the list is scrolled to the end to show the new item.
I tried the accepted solution and it didn't work for me. I use the browser debugger and found out the actual height that should be used is the clientHeight BUT you have to put this into the updated() hook for the whole solution to work.
data(){
return {
conversation: [
{
}
]
},
mounted(){
EventBus.$on('msg-ctr--push-msg-in-conversation', textMsg => {
this.conversation.push(textMsg)
// Didn't work doing scroll here
})
},
updated(){ <=== PUT IT HERE !!
var elem = this.$el
elem.scrollTop = elem.clientHeight;
},
Use the ref attribute on the DOM element for reference
<div class="content scrollable" ref="msgContainer">
<!-- content -->
</div>
You need to setup a WATCH
data() {
return {
count: 5
};
},
watch: {
count: function() {
this.$nextTick(function() {
var container = this.$refs.msgContainer;
container.scrollTop = container.scrollHeight + 120;
});
}
}
Ensure you're using proper CSS
.scrollable {
overflow: hidden;
overflow-y: scroll;
height: calc(100vh - 20px);
}
Here is a simple example using ref to scroll to the bottom of a div.
/*
Defined somewhere:
var vueContent = new Vue({
el: '#vue-content',
...
*/
var messageDisplay = vueContent.$refs.messageDisplay;
messageDisplay.scrollTop = messageDisplay.scrollHeight;
<div id='vue-content'>
<div ref='messageDisplay' id='messages'>
<div v-for="message in messages">
{{ message }}
</div>
</div>
</div>
Notice that by putting ref='messageDisplay' in the HTML, you have access to the element through vueContent.$refs.messageDisplay
If you need to support IE11 and (old) Edge, you can use:
scrollToBottom() {
let element = document.getElementById("yourID");
element.scrollIntoView(false);
}
If you don't need to support IE11, the following will work (clearer code):
scrollToBottom() {
let element = document.getElementById("yourID");
element.scrollIntoView({behavior: "smooth", block: "end"});
}
Try vue-chat-scroll:
Install via npm: npm install --save vue-chat-scroll
Import:
import Vue from 'vue'
import VueChatScroll from 'vue-chat-scroll'
Vue.use(VueChatScroll)
in app.js after window.Vue = require('vue').default;
then use it with :
<ul class="messages" v-chat-scroll>
// your message/chat code...
</ul>
For those that haven't found a working solution above, I believe I have a working one. My specific use case was that I wanted to scroll to the bottom of a specific div - in my case a chatbox - whenever a new message was added to the array.
const container = this.$el.querySelector('#messagesCardContent');
this.$nextTick(() => {
// DOM updated
container.scrollTop = container.scrollHeight;
});
I have to use nextTick as we need to wait for the dom to update from the data change before doing the scroll!
I just put the above code in a watcher for the messages array, like so:
messages: {
handler() {
// this scrolls the messages to the bottom on loading data
const container = this.$el.querySelector('#messagesCard');
this.$nextTick(() => {
// DOM updated
container.scrollTop = container.scrollHeight;
});
},
deep: true,
},
The solution did not work for me but the following code works for me. I am working on dynamic items with class of message-box.
scrollToEnd() {
setTimeout(() => {
this.$el
.getElementsByClassName("message-box")
[
this.$el.getElementsByClassName("message-box").length -
1
].scrollIntoView();
}, 50);
}
Remember to put the method in mounted() not created() and add class message-box to the dynamic item. setTimeout() is essential for this to work. You can refer to https://forum.vuejs.org/t/getelementsbyclassname-and-htmlcollection-within-a-watcher/26478 for more information about this.
This is what worked for me
this.$nextTick(() => {
let scrollHeight = this.$refs.messages.scrollHeight
window.scrollTo(0, scrollHeight)
})
In the related question you posted, we already have a way to achieve that in plain javascript, so we only need to get the js reference to the dom node we want to scroll.
The ref attribute can be used to declare reference to html elements to make them available in vue's component methods.
Or, if the method in the component is a handler for some UI event, and the target is related to the div you want to scroll in space, you can simply pass in the event object along with your wanted arguments, and do the scroll like scroll(event.target.nextSibling).
I had the same need in my app (with complex nested components structure) and I unfortunately did not succeed to make it work.
Finally I used vue-scrollto that works fine !
My solutions without modules:
Template
<div class="scrollable-content" ref="conversations" />
Script
scrollToBottom() {
const container = this.$refs.conversations;
container.scrollTop = container.scrollHeight;
},
scrollToBottom() {
this.$nextTick(function () {
let BoxEl = document.querySelector('#Box');
if(BoxEl)
BoxEl.scrollTop = BoxEl.scrollHeight;
});
}
Agree with Lurein Perera
Just want to add extra info
watch: {
arrayName: {
handler() {
const container = this.$el.querySelector("#idName");
this.$nextTick(() => {
container.scrollTop = container.scrollHeight;
});
},
deep: true,
},
},
Where as:
arrayName = Name of array
idName = The id attribute has to be added to the div where you want the scrollbar to auto-scroll down when arrayName length increases.
scrollToElement() {
const element = this.$refs.abc; // here abc is the ref of the element
if (element) {
el.scrollIntoView({behavior: 'smooth'});
}
}
}
here you need to use ref for the particular div or element which you want make visible on scroll.
if you have a table and you want to locate the last row of the table then you have to use -
element.lastElementChild.scrollIntoView({behaviour:'smooth'})
Here not that if you ware asynchronously adding the element to the table then you have to take care of it. you can test it using setTimeout, if that is making any difference.
e.g.
const element = this.$refs.abc;
if (element) {
setTimeout(() => {
element.lastElementChild.scrollIntoView({behaviour:'smooth'})
}, 1000);
}
}
replace set timeout with your own async logic.
Using Composition API and TypeScript
I set the parameter scrollTop equal to scrollHeightfrom the HTMLDivElment API.
<template>
<div id="container" ref="comments">
Content ...
</div>
</template>
<script lang="ts">
import { defineComponent, ref, Ref, watchEffect } from 'vue'
export default defineComponent({
setup() {
const comments: Ref<null | HTMLDivElement> = ref(null)
watchEffect(() => {
if(comments.value) {
comments.value.scrollTop = comments.value.scrollHeight
}
})
return {
comments
}
}
})
</script>
I have the following:
<div class="container">
Test 1
<div class="child">Testing</div>
</div>
<div class="container">
Test 2
<div class="child">Testing</div>
</div>
<div class="container">
Test 3
<div class="child">Testing</div>
</div>
I wish to have the child div inside the container thats hovered over to show and hide when the mouse has left the container.
I do currently have:
$('.container').hover(
function () {
$(this).next('.child').show();
},
function () {
$(this).next('.child').hide();
}
);
However it does not seem to work.
Any advice appreciated, thanks.
next() is for siblings, you should use children for children :)...
$('.container').hover(
function () {
$(this).children('.child').show();
},
function () {
$(this).children('.child').hide();
}
);
use find() instead of next as it is a child element and not a sibling one:
$('.container').hover(
function () {
$(this).find('.child').show();
},
function () {
$(this).find('.child').hide();
}
);