react-intersection-observer not working mobile view - javascript

Im trying to use react-intersection-observer and framer motion in conjunction with each other in order to start animation when the component is in view using the useInView hook.
Although it works from about 1100px onwards it doesnt work for mobile views.
This is my code below
import Image from "next/image";
import { useRouter } from "next/router";
import { motion, useAnimation } from "framer-motion";
import profile from "../images/profile.jpg";
import { useInView } from "react-intersection-observer";
import { useEffect } from "react";
const HeroSection = () => {
const router = useRouter();
const { ref, inView } = useInView({
threshold: 0.2,
});
const animation = useAnimation();
const heroAnimation = {
hide: {
x: -1000,
opacity: 0,
},
show: {
x: 0,
opacity: [0.1, 1],
transition: { delay: 0.3, duration: 0.75, type: "spring", bounce: 0.2 },
},
};
useEffect(() => {
if (!inView) {
animation.start("hide");
}
if (inView) {
animation.start("show");
}
}, [inView, animation]);
console.log(inView);
return (
<motion.div
animate={animation}
variants={heroAnimation}
ref={ref}
className='space-y-10 px-4 flex flex-col md:grid md:grid-cols-2 md:py-12 bg-white pb-[50px]'>
<div className='flex flex-col justify-center items-center space-y-10'>
<h1 className='text-5xl mt-[50px] md:mt-0'>Welcome!</h1>
<p className='text-center max-w-[400px] lg:max-w-[650px]'> website intro
</p>
<button
onClick={() => router.push("/about")}
className='bg-gray-200 shadow-md text-gray-700 font-bold p-3 lg:p-4 rounded-lg active:bg-gray-300 active:shadow-lg hover:scale-105 transition duration-200 ease-in-out'>
READ MORE..
</button>
</div>
<div className='rounded-lg relative m-auto h-[350px] w-[280px] md:h-[400px] md:w-[320px] lg:h-[500px] lg:w-[400px]'>
<Image
className='rounded-lg'
objectFit='contain'
layout='fill'
src={profile}
alt='profile'
/>
</div>
</motion.div>
);
};
export default HeroSection;

Had a similar issue. Turned out it was working, the initial x value was just too large. This should work fine.
const heroAnimation = {
hide: {
x: -50,
opacity: 0,
},
show: {
x: 0,
opacity: [0.1, 1],
transition: { delay: 0.3, duration: 0.75, type: "spring", bounce: 0.2 },
},
};

Related

Dynamically create object literals based on prop title and color

I am working on my Next.js website. Using Tailwind CSS I have managed to change the color dynamically before returning a component with static names & colors.
Now I am fetching data from the API and the title + color needs to be dynamically created with the values from the API.
the title prop equals the hardcoded project (1,2,3) name. The color should be color prop.
Is there an intelligent way to create an object literal with the dynamic data?
Hardcoded values work just perfectly fine.
import { motion } from 'framer-motion';
import Image from 'next/image';
import Link from 'next/link';
import { projectsArrayProps } from '#typings/propTypes';
const Projects = ({ allProjects }) => {
console.log(allProjects);
return (
<div className="card--grid grid grid-cols-3 lg:grid-cols-4 gap-4 auto-rows-[100px] sm:auto-rows-[120px] md:auto-rows-[200px]">
{allProjects.map(
({ id, logo, title, color }: projectsArrayProps, index) => {
// const projectColor: { [key: string]: string } = {
// project1: 'bg-[#fbc340]/[0.07]',
// project2: 'bg-[#70d1db]/[0.07]',
// project3: 'bg-[#ea5b52]/[0.07]',
// project4: 'bg-[#ff1e00]/[0.07]',
// project5: 'bg-[#ff99f3]/[0.07]',
// };
const projectColor: { [key: string]: string } = {};
return (
<motion.div
key={id}
className={`rounded-md bg-[${projectColor['title']}]`}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{
duration: 0.1,
stiffness: 200,
delay: index * 0.085,
type: 'spring'
}}
variants={{
hidden: { opacity: 0, scale: 0.6 },
visible: { opacity: 1, scale: 1 }
}}
>
<Link href="">
<a
target="_blank"
className="flex flex-col items-center justify-center w-full h-full"
>
<div className="flex flex-col items-center justify-center relative w-full h-full max-w-[64px] md:max-w-[100px] lg:max-w-[120px]">
<img
className="object-contain"
src={logo.url}
alt={logo.alt}
/>
</div>
</a>
</Link>
</motion.div>
);
}
)}
</div>
);
};
export default Projects;
Tailwind extracts classes at build time, so you cannot create dynamic classes at runtime. You could safelist some classes to make sure they are created even when they are not present in the code at build time (https://tailwindcss.com/docs/content-configuration#safelisting-classes). Unfortunately this only helps you if you know all possible colors that can be returned from the API.

Swiping Element Overflow ( Collision Not Working Properly)

I'm working on creating a swiping effect ( Drag to show modal). When ever I swipe the element the element overflow the screen , I use getBoundingClientRect() to check for collision but it's not working correctly for some reasons.
This is the code Below
Note: ClassNames are Tailwinds
<template>
<div id="page" class="fixed top-0 left-0 bottom-0 right-0">
<div class="relative h-full overflow-y-hidden">
<div
ref="dragable"
class="absolute left-0 right-0 h-full -bottom-[45%] bg-purple-200 p-2 border-1 rounded-t-xl"
#touchstart="onTouchStart"
#touchmove="onTouchMove"
#touchend="onTouchEnd">
{{ touchTimer }}
</div>
</div>
</div>
</template>
<script>
export default {
data(){
return {
initialY: 0,
currentY: 0,
pageY: 0,
collide: false,
touchTimer: 0,
touchInterval: null
};
},
computed: {
dragable(){
return this.$refs.dragable;
}
},
methods: {
onTouchStart(event){
this.initialY = event.touches[0].clientY;
event.currentTarget.style.transition = "unset";
},
onTouchMove(event){
this.currentY = event.touches[0].clientY - this.initialY;
const view = event.currentTarget;
const { top } = view.getBoundingClientRect();
if(this.currentY && !this.collide)
view.style.transform = `translate(0, ${this.currentY}px)`;
this.collide = top < 0;
},
onTouchEnd(event){
const view = event.currentTarget;
if(!this.collide)
view.style.transform = "translate(0,0)";
}
},
mounted(){
document.body.ondblclick = function (){
document.body.requestFullscreen();
}
}
};
</script>
<style>
body{ -webkit-overscroll-behavior: contain; }
</style>

Gridstack.js + Vue 3 components

I'm trying to create a gridstack.js dashboard with Vue 3 and I want the grid stack items to contain reactive vue 3 components.
The problem is that these grid stack items can only be passed HTML. The documentation states you should be able to add Vue components as content but the examples are for Vue 2 and I'm struggling to implement this in Vue 3.
I have the following code:
<template>
<div class="p-6 h-full flex flex-col">
<header class="flex flex-row items-center justify-between mb-6">
<div>
<h1 class="text-3xl font-bold">
Workbench
</h1>
<p class="leading-6 text-gray-600 text-sm mt-2">
{{ info }}
</p>
</div>
<div class="flex flex-row items-center">
<button type="button" #click="addPanel()">Add Panel</button>
</div>
</header>
<div class="flex-1">
<section class="grid-stack"></section>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, defineComponent, createApp } from "vue"
import TestPanel from "./../components/panels/TestPanel.vue"
let grid = null;
const items = [
{ x: 0, y: 0, h: 4, w: 6 },
{ x: 7, y: 0, h: 4, w: 6 },
{ x: 0, y: 5, h: 4, w: 4 },
{ x: 4, y: 5, h: 4, w: 4 },
{ x: 8, y: 5, h: 4, w: 4 },
];
onMounted(() => {
grid = GridStack.init({
// float: true,
cellHeight: "70px",
minRow: 1,
});
grid.load(items)
});
function addPanel() {
const div = document.createElement("div")
div.id = Math.random().toString(24).substring(8)
const componentInstance = defineComponent({
extends: TestPanel, data() {
return {
test: "this is a test"
}
}
})
const app = createApp(componentInstance)
app.mount(div)
let widget = grid.addWidget({
x: 0,
y: 0,
w: 6,
h: 3,
content: div.outerHTML,
})
app.mount(div.id)
}
</script>
<style>
.grid-stack-item-content {
background-color: #18BC9C;
}
</style>
This will load the vue component in a stack grid item but the component is no longer reactive.
Any help would be greatly appreciated, thanks in advance!
This is probably not exactly what the gridstack-creators had in mind but here you go:
<template>
<button #click="addNewWidget()">Add Widget</button> {{ info }}
<section class="grid-stack">
<div
v-for="(component, key, index) in components"
:key="'component'+index"
:gs-id="key"
class="grid-stack-item"
:gs-x="component.gridPos.x"
:gs-y="component.gridPos.y"
:gs-h="component.gridPos.h"
:gs-w="component.gridPos.w"
gs-auto-position="true"
>
<div class="grid-stack-item-content">
<component :is="component.name" v-bind="component.props" />
</div>
</div>
</section>
</template>
<script>
import { ref, onMounted, reactive, nextTick } from 'vue';
import 'gridstack/dist/gridstack.min.css';
import { GridStack } from 'gridstack';
import YourRandomComponent1 from '../YourRandomComponent1.vue';
import YourRandomComponent2 from '../YourRandomComponent2.vue';
import YourRandomComponent3 from '../YourRandomComponent3.vue';
export default {
name: "WidgetGrid",
setup() {
let info = ref("");
let grid = null;
let components = reactive({
yourRandomComponent1: {
name: "YourRandomComponent1", props: {}, gridPos: { x: 0, y: 1, w: 4, h: 5 }
},
yourRandomComponent2: {
name: "YourRandomComponent2", props: {}, gridPos: { x: 0, y: 1, w: 2, h: 5 }
},
});
onMounted(() => {
grid = GridStack.init({
float: true,
cellHeight: "70px",
minRow: 1,
});
grid.on("dragstop", (event, element) => {
console.log("move event!", event, element);
const node = element.gridstackNode;
info.value = `you just dragged node #${node.id} to ${node.x},${node.y} – good job!`;
});
});
// this will of course only work once because of the object-key
function addNewWidget() {
components.yourRandomComponent3= {
name: "YourRandomComponent3", props: {}, gridPos: { x: 0, y: 1, w: 2, h: 5 }
};
// we have to wait for vue to update v-for,
// until then the querySelector wont find the element
nextTick(() => {
console.log(grid);
let compEl = document.querySelector('[gs-id="yourRandomComponent3"]');
console.log(compEl);
grid.makeWidget(compEl);
});
console.warn("i will only work once, fix my inputs to reuse me");
}
return {
info,
components,
};
},
components: {
// eslint-disable-next-line vue/no-unused-components
YourRandomComponent1,
// eslint-disable-next-line vue/no-unused-components
YourRandomComponent2,
},
}
</script>
<style>
.grid-stack {
background-color: #FAFAFF;
border-style: dashed;
}
.grid-stack-item {
color: #2c3e50;
text-align: center;
border-style: solid;
overflow: auto;
z-index: 50;
}
</style>
In my case, a missing div with the grid-stack-item-content-class wrapping the component made the widgets immobile.
I have also added an add-new-widget function that demonstrates how to add a new widget to the grid. The key is to use reactive() so that Vue will re-render the page. After re-rendering, the component needs to be registered as a grid element using grid.makeWidget. For this we need the component's Dom element, which we get after Vue has re-rendered with nextTick.
You can use own component in Vue3 like this
<div class="grid-stack" :style="{ 'background-color': hex }" >
<widget v-for="widget in widgets" :widget="widget" :key="widget" />
</div>
Import your component
import Widget from "src/components/GridStackComponent.vue";
Add component to export
export default {
name: 'GridStack',
components: {
Widget
},
data() {
...
},
...
}
And that's all. Result can look like this

How do you animate menu with framer motion on-click?

(Using React obviously + Gatsby)
I have a hamburger button that gonna open a nav-menu in my website.
I wanted to know how to make the menu open with an animation using Framer Motion.
you could use this method that is given in the examples section of the framer motion documentation.
Framer Motion API Documentation
import { motion } from "framer-motion"
const variants = {
open: { opacity: 1, x: 0 },
closed: { opacity: 0, x: "-100%" },
}
export const MyComponent = () => {
const [isOpen, setIsOpen] = useState(false)
return (
<motion.nav
animate={isOpen ? "open" : "closed"}
variants={variants}
>
'Menu Content'
</motion.nav>
)
}
<MonkeyPic rotate={showFistBump} />
// ...
// Then switch animation variant depending on rotate prop
const variants = {
rotate: { rotate: [0, -30, 0], transition: { duration: 0.5 } },
// You can do whatever you want here, if you just want it to stop completely use `rotate: 0`
stop: { y: [0, -10, 0], transition: { repeat: Infinity, repeatDelay: 3 } }
};
const MonkeyPic = ({ rotate }) => {
return (
<div>
<motion.img
variants={variants}
animate={rotate ? 'rotate' : 'stop'}
id="monkeyFace"
src="/images/Monkey.png"
/>
</div>
);
};

How to make vue swipeable bottom sheet component

i want to make swipeable bottom sheet component, like this https://manufont.github.io/react-swipeable-bottom-sheet/scroll.html
I have also tried this package https://github.com/atsutopia/vue-swipeable-bottom-sheet
but this package not to sweep in all sheets areas.
and i try to do something with vue-recognizer to catch pan events , but it end up like this.
<template>
<div>
<div
class="fixed bg-white rounded w-full text-center"
v-recognizer:pan.end="onPanEnd"
:style="{bottom: '0px', height: height + 'px'}"
v-recognizer:pan.up="onPanUp"
v-recognizer:pan.down="onPanDown"
style="z-index:1000;"
>
<div class="py-4">{{height}}</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
height: 92,
};
},
methods: {
onPanUp() {
this.height = parseInt(this.height + 5);
},
onPanDown() {
this.height = parseInt(this.height - 5);
},
onPanEnd() {
},
},
};
</script>
any suggestions ?

Categories