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>
Related
import React, { Component } from 'react'
class ProgressBar extends Component {
render() {
let progressContainer = document.querySelector('.progress-container');
let valueContainer = document.querySelector('.progress-value');
const speed = 20;
let progressValue = 0;
let progressEndValue = 70;
function updateElements() {
valueContainer = document.querySelector('.progress-value');
progressContainer = document.querySelector('.progress-container');
}
const createProgress = setInterval(() => {
progressValue++;
updateElements();
valueContainer.innerText = `${progressValue} %`
progressContainer.style.background = `conic-gradient(
rgb(239 68 68) ${progressValue * 3.6}deg,
black 1deg,
rgb(241 245 249) 1deg,
)`
if (progressValue == progressEndValue) {
clearInterval(createProgress);
}
}, speed)
return (
<div className='progress progress-container w-full h-full rounded-full flex justify-center items-center'>
<div className="progress w-3/4 h-3/4 rounded-full bg-slate-100 flex justify-center items-center">
<h1 className='progress-value' >0 %</h1>
</div>
</div>
)
}
}
export default ProgressBar;
So here is my code, I am basically trying to create a dynamic animated circular progress bar here.
I use updateElements function to prevent the uncaught error of null, the progress value is changing between 0 and 70 percent successfully in the DOM. but the conic-gradient background does not applying in the DOM from the function. but if I set it statically in the CSS file with the same code. it works.
Someone help me please I am struggling since yesterday!!
import React, { Component } from 'react'
class ProgressBar extends Component {
state={
progressValue:0,
speed:20,
progressEndValue:70
}
render() {
let progressContainer = document.querySelector('.progress-container');
let valueContainer = document.querySelector('.progress-value');
function helperFunctions() {
valueContainer = document.querySelector('.progress-value');
progressContainer = document.querySelector('.progress-container');
}
const createProgress = setInterval(() => {
if (this.state.progressValue <= this.state.progressEndValue) {
this.setState({progressValue:this.state.progressValue+1});
helperFunctions();
valueContainer.innerText = `${this.state.progressValue} %`
progressContainer.style.background = `conic-gradient(rgb(239 68 68) ${this.state.progressValue * 3.6}deg,black 1deg,rgb(241 245 249) 1deg)`
} else {
clearInterval(createProgress);
}
}, this.state.speed)
return (
<div className='progress progress-container w-full h-full rounded-full flex justify-center items-center'>
<div className="progress w-3/4 h-3/4 rounded-full bg-slate-100 flex justify-center items-center">
<h1 className='progress-value' >0 %</h1>
</div>
</div>
)
}
}
export default ProgressBar;
Now it works fine :)
suggestion:
If your were using functional component, it could be done much easier and you could use useRef intead of document.querySelector as it is recomanded is React document
The main problem was the last , in conic-gradient
So I'm trying to make a dynamic reusable switch selector component exactly like that on https://www.themoviedb.org/ to select between a number of options such as ["a", "b", "c"].
I've got most of the logic down, it might be a bit messy now, but my problem really is that I can't seem to figure out at which width or distance to move the coloured div to accurately place it right on top of the label/option title.
This is what I've got so far, the text colour changes correctly when selected, and the transaction is also smooth, but the position is wrong.
type SwitchProps = {
optionTitles: string[];
};
type Selector = {
isToggled: boolean;
optionTitle: string;
width: number | undefined;
};
const Switch: FC<SwitchProps> = (props) => {
const [selectors, setSelectors] = useState<Selector[]>([]);
const [currentToggled, setCurrentToggled] = useState<Selector & { index: number }>({
index: 0,
isToggled: true,
optionTitle: props.optionTitles[0],
width: 110,
});
const elementsRef = useRef<RefObject<HTMLDivElement>[]>(props.optionTitles.map(() => createRef()));
useLayoutEffect(() => {
if (selectors.length >= props.optionTitles.length) {
return;
}
props.optionTitles.map((optionName, index) => {
setSelectors((prevState) => [
...prevState,
{
isToggled: index === 0 ? true : false,
optionTitle: optionName,
width: 110,
},
]);
});
}, []);
const handlerToggleClick = (sectorIndex: number, toggleState: boolean) => {
let data = selectors;
data.forEach((selector, index) => {
selector.isToggled = false;
selector.width = elementsRef.current[sectorIndex].current?.offsetWidth;
});
data[sectorIndex].isToggled = true;
setCurrentToggled({ index: sectorIndex, ...data[sectorIndex] });
setSelectors(data);
};
return (
<div className="relative z-[1] h-8 border border-solid border-tmdbDarkBlue rounded-[30px] font-medium flex items-center">
{selectors.map((sector, index) => (
<div
key={index}
ref={elementsRef.current[index]}
className={`py-1 px-5 h-8 text-sm font-semibold flex items-center ${
sector.isToggled && "switch-active-text"
}`}
>
<span
className="cursor-pointer flex items-center"
onClick={() => handlerToggleClick(index, !sector.isToggled)}
>
{sector.optionTitle}
</span>
</div>
))}
<div
className={`absolute z-[-1] h-8 w-20 bg-tmdbDarkBlue rounded-[30px] transition-all duration-150 ease-in`}
style={{
width: currentToggled.width,
left: currentToggled.index === 0 ? 0 : (currentToggled.width as number) * 1.8,
}}
></div>
</div>
);
};
export default Switch;
If there are other ways to improve my code, please do let me know. I'm trying to get better at things, which is why I'm working on this clone sorta project.
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 },
},
};
I'm trying to build a toast notification component in Alpine.js. But I'm not getting to send notification from vanilla js files.
<div
x-data="
{
notices: [],
visible: [],
add(notice) {
notice.id = Date.now()
this.notices.push(notice)
this.fire(notice.id)
},
fire(id) {
this.visible.push(this.notices.find(notice => notice.id == id))
const timeShown = 10000 * this.visible.length
setTimeout(() => {
this.remove(id)
}, timeShown)
},
remove(id) {
const notice = this.visible.find(notice => notice.id == id)
const index = this.visible.indexOf(notice)
this.visible.splice(index, 1)
},
}
"
class="z-50 p-7 fixed inset-0 w-screen flex flex-col-reverse items-end justify-end pointer-events-none"
#notice.window="add($event.detail)">
<template x-for="notice of notices" :key="notice.id">
<div
x-show="visible.includes(notice)"
x-transition:enter="transition ease-in duration-400"
x-transition:enter-start="transform opacity-0 translate-x-full"
x-transition:enter-end="transform opacity-100"
x-transition:leave="transition ease-out duration-500"
x-transition:leave-start="transform translate-x-0 opacity-100"
x-transition:leave-end="transform translate-x-full opacity-0"ยง
#click="remove(notice.id)"
class="rounded mb-4 p-3 w-full max-w-md text-green-800 shadow-lg cursor-pointer pointer-events-auto bg-green-200"
x-text="notice.text">
</div>
</template>
</div>
https://codepen.io/nuno360/pen/WNGKmeL
From vanilla js file how I can send notices to this component?
You can create a custom event and dispatch it (assuming you're selecting an element of some sort) with element.dispatchEvent(new CustomEvent('notice', { detail: {}, bubbles: true }))
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 ?