I made a scroll to top and scroll to bottom button. I would like to use my scroll to top and scroll to bottom as globally in my apps. This is because i have to keep copy hasScroll:false, hasVerticalScroll(){this.hasScroll=document.body.offsetHeight > window.innerHeight}; , declare <v-app v-scroll="hasVerticalScroll"> and <ScrollToTop v-if="hasScroll"/>in every pages that need this component. Is there any way to reduce the code so that i do not have to repeat all the codes in every pages to call the component?
Default.vue
<script>
export default {
name: "DefaultLayout",
data() {
return {
hasScroll: false
};
},
methods: {
hasVerticalScroll() {
this.hasScroll = document.body.offsetHeight > window.innerHeight;
}
},
watch: {
'$route' (to, from) {
this.hasVerticalScroll()
}
}
}
</script>
<template>
<v-app dark v-scroll="hasVerticalScroll">
<ScrollToTop v-if="hasScroll"/>
</v-app>
</template>
ScrollToTop.vue
<template>
<div v-scroll="onScroll" >
<v-btn v-if = "!isVisible"
fab fixed bottom right color="primary" #click="toBottom" >
<v-icon>mdi-arrow-down-bold-box-outline</v-icon>
</v-btn>
<v-btn v-else
fab fixed bottom right color="primary" #click="toTop">
<v-icon>mdi-arrow-up-bold-box-outline</v-icon>
</v-btn>
</div>
</template>
<script>
export default{
data () {
return {
isVisible: false,
position: 0,
}
},
methods: {
onScroll () {
this.isVisible = window.pageYOffset> 50
},
toTop () {
this.position = window.pageYOffset
window.scrollTo({
top: 0,
left: 0,
behavior: 'smooth'
})
},
toBottom(){
let pos = this.position > 0 ? this.position : document.body.scrollHeight
window.scrollTo({
top: document.body.scrollHeight,
behavior: 'smooth'
})
},
}
}
</script>
If I understood you correctly, then you need to add a global event listener to ScrollToTop.vue and you don't need to use vuetify's v-scroll directive at all.
Example:
mounted() {
this.listener = () => {
this.isVisible = window.pageYOffset> 50
};
document.addEventListener('scroll', this.listener);
},
beforeUnmount() {
document.removeEventListener('scroll', this.listener);
},
Related
I've tested with CSS/SCSS so far, it seems to work only on first page change :
.page-leave-active {
.news-item {
#for $i from 1 through 10 {
transition-delay: $i * 300ms;
}
transform: translateX(115%);
}
}
I was wondering is there a way to add JS transitions inside a component? Is there an equivalent to mounted, but for unmount?
if you don't use dynamic page transition name for ssr-nuxt page transition, just add the code in transition attribute like examples in nuxtjs document
if you are using a dynamic page transition name, it's a little hard if you use transition attribute that nuxt provided. I use below code for dynamic page transition name, and it works well for me(don't forget code your own <error-view/> component)
use this instead of <nuxt/>, you can add your logic easily in methods
<template>
<main>
<transition
mode="out-in"
:name="transitionName"
#beforeLeave="beforeLeave"
#enter="enter"
#afterEnter="afterEnter">
<router-view v-if="!nuxt.err"/>
<error-view :error="nuxt.err" v-else/>
</transition>
</main>
</template>
<script type="text/javascript">
import Vue from 'vue';
import ErrorView from '#/layouts/error';
export default{
components: {
ErrorView
},
data() {
return {
prevHeight: 0,
transitionName: 'fade'
};
},
beforeCreate () {
Vue.util.defineReactive(this, 'nuxt', this.$root.$options.nuxt);
},
created() {
this.$router.beforeEach((to, from, next) => {
let transitionName = to.meta.transitionName || from.meta.transitionName;
if (transitionName === 'slide') {
const toDepth = to.path.split('/').length;
const fromDepth = from.path.split('/').length;
transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left';
}
this.transitionName = transitionName || 'fade';
next();
});
},
methods: {
beforeLeave(el) {
this.prevHeight = getComputedStyle(el).height;
},
enter(el) {
const { height } = getComputedStyle(el);
el.style.height = this.prevHeight;
setTimeout(() => {
el.style.height = height;
}, 0);
},
afterEnter(el) {
el.style.height = 'auto';
}
}
}
</script>
Here is what you get
Full resource code of the demo that gif picture shows is here
I've got some questions about vuejs and router ..
window.addEventListener('scroll', ...) also is not detected in my component.
When I typed 'window.scrollY' in console.log. It will always return 0 back to me.
Right scroll(Y) are available and window.innerHeight is not equal 0
I can't detected when client move scroll to bottom
I use vuestic and vue-router
Thank you
created () {
// Not working because window.scrollY always return 0
window.addEventListener('scroll', this.handleScroll);
},
methods: {
handleScroll (event) {}
}
you can try to listen for scrolling a child element.
and using getBoundClientRect:
<template>
<div id="app">
<nav>navbar</nav>
<main id="listen">main</main>
</div>
</template>
<script>
export default {
name: "App",
created() {
document.addEventListener("scroll", this.listenScroll);
},
destroyed() { // remember to remove the listener when destroy the components
document.removeEventListener("scroll", this.listenScroll);
},
methods: {
listenScroll() {
let myScroll = document.querySelector("#listen").getBoundingClientRect()
.top;
console.log(myScroll);
},
},
};
</script>
<style>
nav {
height: 100px;
}
main {
height: 700px;
}
</style>
here there is a codesandbox https://codesandbox.io/s/great-hill-x3wb1?file=/src/App.vue:0-560
First, the terms, "link" is the area where the mouse enters. The "tooltip" is the thing that pops up and shows extra information.
--- above added 2020-04-29
I'm using Vuetify and trying to keep the v-tooltip open when mouse is hovering over the "tooltip".
The content inside the tooltip is going to be rich and don't want that to automatically hide when visitor is looking into it.
<template>
<v-tooltip
v-model="show"
max-width="600px"
content-class="link-tooltip-content"
bottom>
<template v-slot:activator="{ on }">
<div
:style="boxStyle"
#mouseover="mouseover"
#mouseleave="mouseleave"
></div>
</template>
<template v-slot:default>
<v-card
#mouseover="mouseover"
#mouseleave="mouseleave"
>
<v-row dense>
<v-col>
<v-card-title class="headline">
rich tooltip
</v-card-title>
</v-col>
</v-row>
</v-card>
</template>
</v-tooltip>
</template>
<script>
export default {
data: () => ({
show: false,
hoverTimer: null
}),
methods: {
boxStyle: function() {
return {
left: "100px",
top: "100px",
width: "100px",
height: "100px",
position: "absolute"
};
},
mouseover: function() {
console.log(`mouseover`);
this.show = true;
clearTimeout(this.hoverTimer);
},
mouseleave: function() {
console.log(`mouseleave`);
this.hoverTimer = setTimeout(() => {
this.show = false;
}, 3000);
}
}
};
</script>
But this doesn't work. The mouseover and mouseleave event handlers on the activator slot (the "link") element does fire, but the event handlers on the default slot (the "tooltip") don't fire.
I think the reason is, because the content inside the "tooltip" is moved to somewhere else under the body tag.
The questions is, how can I keep the "tooltip" open when hovering over it.
I'm moving the mouse like this:
Hover over the link (the tooltip shows up).
Move the mouse out of the link and into the tooltip. (The link and tooltip is a few pixels apart)
Now the mouseleave event for the link fires, and I want to add a mouseenter event handler on the tooltip. How do I do that ?
I'm thinking to add an mouseenter event on the tooltip, so that I can clearTimeout(hoverTimer) and keep the tooltip open.
I know there's a similar question from 9 years ago, using jQuery Keep tooltip opened when the mouse is over it , but I don't want to use jQuery if possible. I prefer a Vue way.
Here's a little reproducible example:
https://www.codeply.com/p/GuFXqAAU8Y
Instead of using v-tooltip, I suggest you use v-menu with the open-on-hover props set to true.
If you have to nudge whatever you put in the menu, make sure to set an appropriate close-delay value, so the menu doesn't close before the user reaches it.
Example:
https://codepen.io/stephane303/pen/WNwdNxY
<v-menu open-on-hover right offset-x nudge-right="20" close-delay="100">
.v-tooltip__content has pointer-events:none set in vuetify.min.css. If you set it back to auto you allow it to be hovered.
When its hovered, its parent is hovered. And when its parent is hovered, it has a tooltip. So all you need is:
.v-tooltip__content {
pointer-events: auto;
}
I made extended version of VTooltip for this purpose. Just pass interactive prop. See working example here by hovering Governance stepper: https://tzkt.io
<script>
/**
* Extends VTooltip with interactivity
* #see https://material-ui.com/components/tooltips/#interactive
*/
import { VTooltip } from 'vuetify/lib';
export default {
extends: VTooltip,
props: {
interactive: {
type: Boolean,
default: false,
},
closeDelay: {
type: [Number, String],
default: 50,
},
},
computed: {
// I'm not 100% sure in this, but it works
calculatedLeft() {
const originalValue = VTooltip.options.computed.calculatedLeft.call(this);
if (!this.interactive) return originalValue;
const { left, right } = this;
let value = parseInt(originalValue);
if (left || right) {
value += right ? -10 : 10;
}
return `${value}px`;
},
calculatedTop() {
const originalValue = VTooltip.options.computed.calculatedTop.call(this);
if (!this.interactive) return originalValue;
const { top, bottom } = this;
let value = parseInt(originalValue);
if (top || bottom) {
value += bottom ? -10 : 10;
}
return `${value}px`;
},
styles() {
const originalValue = VTooltip.options.computed.styles.call(this);
if (!this.interactive) return originalValue;
const {
top, bottom, left, right,
} = this;
let paddingDirection;
if (bottom) paddingDirection = 'top';
else if (top) paddingDirection = 'bottom';
else if (right) paddingDirection = 'left';
else if (left) paddingDirection = 'right';
return {
...originalValue,
[`padding-${paddingDirection}`]: `${10}px`,
};
},
},
methods: {
onTooltipMouseenter(e) {
if (this.interactive) {
this.clearDelay();
this.isActive = true;
}
this.$emit('tooltip:mouseenter', e);
},
onTooltipMouseleave(e) {
if (this.interactive) {
this.clearDelay();
this.runDelay('close');
}
this.$emit('tooltip:mouseleave', e);
},
genContent() {
const content = this.$createElement('div', this.setBackgroundColor(this.color, {
style: this.contentStyles,
staticClass: 'v-tooltip__content',
class: {
[this.contentClass]: true,
menuable__content__active: this.isActive,
},
}), this.getContentSlot());
return this.$createElement('div', {
style: this.styles,
attrs: this.getScopeIdAttrs(),
class: {
'v-tooltip__wrapper': true,
'v-tooltip__wrapper--fixed': this.activatorFixed,
},
directives: [{
name: 'show',
value: this.isContentActive,
}],
on: {
mouseenter: this.onTooltipMouseenter,
mouseleave: this.onTooltipMouseleave,
},
ref: 'content',
}, [content]);
},
genActivatorListeners() {
const listeners = VTooltip.options.methods.genActivatorListeners.call(this);
if (this.interactive) {
if (listeners.mouseenter) {
listeners.mouseenter = (e) => {
this.getActivator(e);
this.clearDelay();
if (!this.isActive) {
this.runDelay('open');
}
};
}
}
return listeners;
},
},
};
</script>
<style lang="scss">
.v-tooltip__wrapper {
position: absolute;
&--fixed {
position: fixed;
}
.v-tooltip__content {
position: static;
}
}
</style>
I am very new to vue.js and fumbling my way though it, forgive me if my terms are incorrect. I am creating a touchscreen application that needs to be ADA compliant (only the bottom part of the screen is accessible, so i have to use buttons for interaction).
I have a parent component with a carousel creating an array of slides, pulling data from my child component.
parent component HTML
<carousel :navigateTo="selectedListIndex" #pageChange="OnPageChange">
<slide v-for="(member, index) in selectedList" :key="index">
<MemberBioPage :member="member"/>
</slide>
</carousel>
parent component SCRIPT:
export default {
data () {
return {
currentPage: 0
}
},
components: {
MemberBioPage,
Carousel,
Slide
},
computed: {
selectedList () {
return this.$store.state.selectedList
},
selectedListIndex () {
return this.$store.state.selectedListIndex
}
},
methods: {
OnPageChange (newPageIndex) {
console.log(newPageIndex)
this.currentPage = newPageIndex
}
}
}
within my child component, i have bio copy being pulled from my data and arrow buttons that allow you to scroll the text. There is an outer container and an inner container to allow the scrolling and based on the height that the content takes up in the container will determine when the arrows disable or not.
child component HTML:
<div class="member-bio-page">
<div class="bio">
<div class="portrait-image">
<img :src="member.imgSrc" />
</div>
<div class="bio-container">
<div class="inner-scroll" v-bind:style="{top: scrollVar + 'px'}">
<h1>{{ member.name }}</h1>
<div class="description-container">
<div class="para">
<p v-html="member.shortBio"></p>
</div>
</div>
</div>
</div>
<div class="scroll-buttons">
<div>
<!-- set the class of active is the scroll variable is less than 0-->
<img class="btn-scroll" v-bind:class="{ 'active': scrollVar < 0 }" #click="scrollUp" src="#/assets/arrow-up.png">
</div>
<div>
<!-- set the class of active is the scroll variable is greater than the height of the scrollable inner container-->
<img class="btn-scroll" v-bind:class="{ 'active': scrollVar > newHeight }" #click="scrollDown" src="#/assets/arrow-down.png">
</div>
</div>
</div>
</div>
child component SCRIPT:
<script>
export default {
props: [
'member', 'currentPage'
],
data () {
return {
scrollVar: 0,
outerHeight: 0,
innerHeight: 0,
newHeight: -10
}
},
mounted () {
this.outerHeight = document.getElementsByClassName('bio-container')[0].clientHeight
this.innerHeight = document.getElementsByClassName('inner-scroll')[0].clientHeight
this.newHeight = this.outerHeight - this.innerHeight
return this.newHeight
},
methods: {
scrollUp () {
console.log(this.scrollVar)
this.scrollVar += 40
},
scrollDown () {
console.log(this.scrollVar)
this.scrollVar -= 40
},
showVideo () {
this.$emit('showContent')
}
}
}
</script>
I am able to get the height of the first bio i look at, but on page change it keeps that set height. I basically want the code in mounted to be able to rerun based on the index of the slide i am on. I need 'newHeight' to update on each page change. I tried grabbing the 'currentPage' from my parent component using props, but it pulls undefined.
here is all a snippet from my data to show you what data i currently have:
{
index: 12,
name: 'Name of Person',
carouselImage: require('#/assets/carousel-images/image.jpg'),
imgSrc: require('#/assets/bio-page-image-placeholder.jpg'),
shortBio: '<p>a bunch of text being pulled</p>',
pin: require('#/assets/image-of-pin.png')
}
this is also my store just in case
const store = new Vuex.Store({
state: {
foundersList: founders,
chairmanList: chairmans,
selectedList: founders,
selectedListIndex: -1
},
mutations: {
setSelectedState (state, list) {
state.selectedList = list
},
setSelectedListIndex (state, idx) {
state.selectedListIndex = idx
}
}
})
Alright, so this is a good start. Here's a few things I would try:
Move the code you currently have in mounted to a new method called calculateHeight or something similar.
Call the method from your scrollUp and scrollDown methods.
So your final code would look something like this:
export default {
props: [
'member', 'currentPage'
],
data () {
return {
scrollVar: 0,
outerHeight: 0,
innerHeight: 0,
newHeight: -10
}
},
mounted () {
this.calculateHeight();
},
methods: {
calculateHeight() {
this.outerHeight = document.getElementsByClassName('bio-container')[0].clientHeight
this.innerHeight = document.getElementsByClassName('inner-scroll')[0].clientHeight
this.newHeight = this.outerHeight - this.innerHeight
},
scrollUp () {
console.log(this.scrollVar)
this.scrollVar += 40
this.calculateHeight()
},
scrollDown () {
console.log(this.scrollVar)
this.scrollVar -= 40
this.calculateHeight()
},
showVideo () {
this.$emit('showContent')
}
}
}
I am working filename.vue file. I am trying to add a div to show up after scroll 100px. I am not able to get it. Please help me find out where I am going wrong.
<template>
<div id="sectionTop">
<div id="scrollButton">
<a class="topButton" href="javascript:document.getElementById('sectionTop').scrollIntoView(true);"><img src="../../images/uparrow.svg"></a>
</div>
</div>
</template>
<script>
$(document).scroll(function() {
var y = $(this).scrollTop();
if (y > 800) {
$('.scrollButton').fadeIn();
} else {
$('.scrollButton').fadeOut();
}
});
</script>
Updated as per suggested
<template>
<div id="sectionTop">
<div id="scrollButton">
<a class="topButton" href="javascript:document.getElementById('sectionTop').scrollIntoView(true);" v-if="scrollpx > 800"><img src="../../images/uparrow.svg"></a>
</div>
</div>
</template>
<script>
export default {
data() {
return {
scrollpx: 0
};
},
methods: {
handleScroll() {
this.scrollpx = window.scrollY;
}
},
created() {
window.addEventListener('scroll', this.handleScroll);
},
destroyed() {
window.removeEventListener('scroll', this.handleScroll);
}
};
</script>
This is how I solved this problem.
<a class="scrollTop" href="javascript:document.getElementById('sectionTop').scrollIntoView(true);" title='Click here to go to the top' v-if="scrollpx > 100"><img src="../../images/uparrow.svg"></a>
<script>
export default {
data() {
return {
scrollpx: 0
};
},
methods: {
handleScroll() {
this.scrollpx = document.body.scrollTop;
}
},
mounted() {
document.body.addEventListener('scroll', this.handleScroll);
}
};
</script>
You don't need jquery to do this. You can register an event listener for scroll events in the created method of your vue component (don't forget to unregister it afterwards!). In you handleScroll method you can then update a variable holding the current scrollY-value. Now this value is available to vue and can be used "reactively". For example, you can show your button using v-if:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="replace"></div>
<script>
new Vue({
el: '#replace',
template: `
<div id="scroller">
<div id="sectionTop" style="height: 5000px">
Please Scroll down
<div id="scrollButton">
<a class="topButton" style="position:fixed" v-if="scrollpx > 800">Scroll top</a>
</div>
</div>
</div>`,
data() {
return {
scrollpx: 0
};
},
methods: {
handleScroll() {
this.scrollpx = window.scrollY;
}
},
created() {
window.addEventListener('scroll', this.handleScroll);
},
destroyed() {
window.removeEventListener('scroll', this.handleScroll);
}
})
</script>