Hammer.js swipe left gesture animation in Angular - javascript

I'm trying to have a swipe animation when user swipes left in the device.
Here's a gist of the problem
When I click on about page button in Home page, the app smoothly navigates to About page.
When I hit on back icon button in the top bar of About page, it gives a back swipe animation.
I've implemented Hammer.js swipe left gesture for going back but it doesn't show any animation as that of back button animation. How could I achieve that?
I created a working example using StackBlitz to better explain the problem. Could anyone please help?
HTML
<ion-content (swipeleft)="goBack()">
<h2>About page</h2>
</ion-content>
TS
export class AboutPage implements OnInit {
constructor(private location: Location) {}
ngOnInit() {}
goBack() {
this.location.back();
}
}

Finally, I got this implemented using Ionic Gestures.
Here's the working example. Swipe left to right from about page, it will result a smooth animation and takes back to the page from you where came from.
Swipe Gesture Animation
async ngAfterViewInit() {
let gesture = await this.gestureCtrl.create({
el: this.aboutPage.nativeElement,
gestureName: 'swipe-left',
gesturePriority: 100,
threshold: 5,
passive: false,
onStart: () => {
this.renderer.setStyle(
this.aboutPage.nativeElement,
'transition',
'none'
);
},
onMove: (ev) => {
if (ev.deltaX > 0) {
this.renderer.setStyle(
this.aboutPage.nativeElement,
'transform',
`translateX(${ev.deltaX}px)`
);
}
},
onEnd: (ev) => {
this.renderer.setStyle(
this.aboutPage.nativeElement,
'transition',
'0.4s ease-out'
);
if (ev.deltaX > 100) {
this.renderer.setStyle(
this.aboutPage.nativeElement,
'transform',
'translateX(400px)'
);
this.location.back();
}
if (ev.deltaX < 100) {
this.renderer.setStyle(
this.aboutPage.nativeElement,
'transform',
'translateX(0px)'
);
}
},
});
gesture.enable(true);
}
Result of the animation

Related

GSAP scrolling breaks when switching browser tabs

I've noticed some strange behavior of GSAP scrolling in GatsbyJS. I have a container with full-page sections that are scrolled with GSAP.
I open a tab in a browser and go to my website. The scrolling works fine. Then I open a new tab and go to (let's say) google.com and spend some time on that website. The tab with my website is still open in a background. Then I switch from google tab back to my website tab. After doing that GSAP 'breaks' and scrolls two full-page sections one after another. Even if I scroll a mouse wheel a bit. And sometimes GSAP can't scroll the section at all. It looks like a full-page starts moving but suddenly the scrolling stops and GSAP places the current section to it's place.
I never go to any other website within that browser tab. Also I never go to any another route of my website. I just open an index page -> switch to another browser tab -> and switch back to my website tab. I have no idea how and why but exactly these actions cause the problem.
P.S. resizing the browser window helps but after some time the scrolling breaks again
Code:
export default function IndexPage() {
useEffect(() => {
gsap.registerPlugin(ScrollTrigger)
gsap.registerPlugin(ScrollToPlugin)
const sections = document.querySelectorAll("section")
const scrolling = {
enabled: true,
events: "scroll,wheel,touchmove,pointermove".split(","),
prevent: e => e.preventDefault(),
disable() {
if (scrolling.enabled) {
scrolling.enabled = false
window.addEventListener("scroll", gsap.ticker.tick, { passive: true })
scrolling.events.forEach((e, i) =>
(i ? document : window).addEventListener(e, scrolling.prevent, {
passive: false,
})
)
}
},
enable() {
if (!scrolling.enabled) {
scrolling.enabled = true
window.removeEventListener("scroll", gsap.ticker.tick)
scrolling.events.forEach((e, i) =>
(i ? document : window).removeEventListener(e, scrolling.prevent)
)
}
},
}
function goToSection(section, anim, i) {
console.log(scrolling.enabled)
if (scrolling.enabled) {
// skip if a scroll tween is in progress
scrolling.disable()
gsap.to(window, {
scrollTo: { y: section, autoKill: false },
onComplete: scrolling.enable,
duration: 1,
})
anim && anim.restart()
}
console.log(section)
}
sections.forEach((section, i) => {
const intoAnim = gsap.from(section.querySelector(".right-col"), {
yPercent: 50,
duration: 1,
paused: true,
})
ScrollTrigger.create({
trigger: section,
start: "top bottom-=1",
end: "bottom top+=1",
onEnter: () => goToSection(section, intoAnim),
onEnterBack: () => goToSection(section),
})
})
return () => {
ScrollTrigger.kill()
}
}, [])
return (
<div className={styles.container}>
<Header />
<Products />
<Contact />
<Footer />
</div>
)
}

How to move plane icon with framer motion?

I have a react paper plane icon that I would like to make a cool effect by making it travel around the HTML document reaching the final position which is the menu button at the top.
This is how my react component called NavPlane looks like:
import React, { Component, useEffect } from "react";
import { useCycle } from "framer-motion";
import { FaPaperPlane } from "react-icons/fa";
import { motion } from "framer-motion";
const PlaneVariants = {
animationOne: {
x: 370,
y: 250,
transition: { ease: [0.17, 0.67, 0.83, 0.67] },
},
animationTwo: {
x: 140,
y: 150,
transition: { duration: 1.0 },
},
animationThree: {
x: 170,
y: 200,
transition: { duration: 1.0 },
},
};
export default function NavPlane() {
const [animation, cycleAnimation] = useCycle(
"animationOne",
"animationTwo",
"animationThree"
);
useEffect(() => {
setTimeout(() => {
cycleAnimation();
}, 1000);
}, []);
return (
<motion.div
className="text-2xl text-gray-600"
variants={PlaneVariants}
animate={animation}
>
<FaPaperPlane />
</motion.div>
);
}
cycleAnimation() was supposed to cycle through 3 animations but only cycling through the first two. The 3rd is only applied when making some change to the code and updating it.
The final goal is to have a movement that goes from right corner of the page to middle, does a knot movement and then travels to the top of the page.
Any ideas how to make it cycle through as many animation variants as I want?
cycleAnimation only advances one step. Initially it will start "animationOne". Once you call cycleAnimation() after 1 second, "animationTwo" will start. You need another timer if you want to play "animationThree"
useEffect(() => {
setTimeout(cycleAnimation, 1000); // start "animationTwo" after 1 second
setTimeout(cycleAnimation, 2000); // start "animationThree" after 2 seconds
}, []);

Keep v-tooltip open when hovering over the tooltip

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>

How to hide ag-grid column on mobile?

I'm trying to hide a column in ag-grid on mobile.
I'm using ag-grid for a vue.js app. I have tired the below code, but it's not working properly.
{
headerName: "Action",
field: "action",
minWidth: 54,
suppressMovable: true,
editable: true,
hide : ( window.innerWidth < 786 ? true : false )
}
I expect the output to be hide this column on mobile and show on desktop, but the output is bit strange to me. Initially when I load the page on mobile and desktop the column hide/show accordingly, but on mobile it's also hide some of other column's header titles(only header titles). Also when I resize the window from mobile to desktop the require column won't show and also resizing from desktop to mobile won't hide the required column.
You should use columnApi.setColumnVisible("your_column", false).
To be able to do so, first save the columnApi on #grid-ready
<template>
<ag-grid-vue style="width: 500px; height: 500px;"
class="ag-theme-balham"
:columnDefs="columnDefs"
:rowData="rowData"
rowSelection="multiple"
#grid-ready="onGridReady">
</template>
...
import {AgGridVue} from "ag-grid-vue";
const hideActionColumn = () => {
if(this.columnApi) {
this.columnApi.setColumnVisible("action", true);
if(window.innerWidth < 786) {
this.columnApi.setColumnVisible("action", false);
}
}
}
export default {
name: 'App',
data() {
return {
columnDefs: null,
rowData: null
}
},
components: {
AgGridVue
},
methods: {
onGridReady(params) {
this.columnApi = params.columnApi;
}
},
mounted() {
// hide the column, if the initial load is in mobile view
hideActionColumn();
window.addEventListener('resize', hideActionColumn);
},
beforeDestroy() {
window.removeEventListener('resize', hideActionColumn);
}
}
Then you need to appends an event listener, so you can call hideActionColumn() on window.resize
window.addEventListener('resize', hideActionColumn);
Also you need to remove the event listener, before your component is destroyed.
I hope this will help you.
Regards

How do I change the ripple background color on Button?

So far in the API (v3.9.2), I see a mention of TouchRippleProps for ButtonBase for https://material-ui.com/api/button-base/
My button looks like
<Button variant="text"
size={"large"}
fullWidth
className={classes.button}
>
{value}
</Button>
and my button style is .
button: {
backgroundColor: colors.surface,
borderRadius: 0, // to make buttons sharp edged
touchRipple: colors.primary
}
When I touch a button, I see a white background (see number 5) as
My question is that When I touch a button, how can I change that background from white to let's say blue and then let it fade away?
UPDATE .
I achieved reasonable behavior with the following changes to your numberPadStyle:
const numberPadStyle = theme => ({
button: {
backgroundColor: colors.surface,
borderRadius: 0, // to make buttons sharp edged
"&:hover": {
backgroundColor: colors.primary,
// Reset on touch devices, it doesn't add specificity
"#media (hover: none)": {
backgroundColor: colors.surface,
"&:active": {
backgroundColor: colors.primary
}
}
},
"&:active": {
backgroundColor: colors.primary
}
}
});
The issue with touch screens is that a touch triggers the "hover" effect and it doesn't go away till you touch somewhere else. "#media (hover: none)" targets the hover effect for touch devices (https://developer.mozilla.org/en-US/docs/Web/CSS/#media/hover). The "active" CSS is in effect during the touch/click and then the ripple built in to Button takes care of the rest.
You can, of course, adjust the hover and active colors as desired.
It doesn't appear animations other than the ripple are supported. However, you can create something like this TriggeredAnimation wrapper component:
class TriggeredAnimationWrapper extends Component {
constructor(...args) {
super(...args)
this.state = {
wasClicked: false
}
this.triggerAnimation = this.triggerAnimation.bind(this)
}
triggerAnimation(callback) {
return (...args) => {
this.setState(
{wasClicked: true},
() => requestAnimationFrame(()=>this.setState({wasClicked: false}))
)
if (callback) return callback(...args)
}
}
render() {
const {
triggerAnimation,
props: {
children
},
state: {
wasClicked
}
} = this.props
return children({wasClicked, triggerAnimation})
}
}
And use it like this:
<TriggeredAnimationWrapper>({wasClicked, triggerAnimation}) => (
<Button
onClick={triggerAnimation((e) => console.log('clicked', e))}
className={wasClicked ? 'clicked' : ''}
/>
)</TriggeredAnimationWrapper>
Then, you can create a css animation and change the background when the click class is present.

Categories

Resources