How to make vue swipeable bottom sheet component - javascript

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 ?

Related

Problem while trying to update style of a Jsx element

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

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

Vue.js: Vue-Carousel jumping to last item in array on click

I am currently working on an application that involves scrolling through members from two different lists. Once you click one you see their image and bio. We added up and down arrows to scroll through their biography, but when i click the down arrow it seems that the active class on the carousel slide now jumps to the last member in my array, but still displays the member i was already on. To add to that I cannot swipe back and forth once i am on a member (this is something that I could do previously.) I know this probably isn't the best description and there is only so much code I can show, as I cannot display the entire application.
I wanted to also note that if I refresh my page, everything works as expected. Does this mean something isn't initializing correctly?
this is the code specific to the page i am talking about:
<template>
<div class="member-info-carousel">
<div class="header">
<h2 v-if="founderChairman === true">Member List One</h2>
<h2 v-else>Member List Two</h2>
<img src="../assets/logo.png" alt="Logo" />
</div>
<carousel :minSwipeDistance="384" :perPage="1" :paginationEnabled="false" :navigationEnabled="true" navigationNextLabel="<i>NEXT</i>"
navigationPrevLabel="<i>BACK</i>" :navigateTo="selectedListIndex" #pagechange="OnPageChange">
<slide v-for="(member, index) in selectedList" :key="index">
<div class="member-bio-page" :member="member" v-on:showContent="showContent">
<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'}">
<div class="english"></div>
<div class="pin-name">
<img :src="member.pin" />
<h1>{{ member.name }}</h1>
</div>
<div class="description-container">
<div class="para">
<p class="quote" v-html="member.quote"></p>
<p v-html="member.bio"></p>
<div class="spanish"></div>
<p class="quote" v-html="member.spanishQuote"></p>
<p v-html="member.spanishBio"></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 > pageChangeHeight }" #click="scrollDown" src="#/assets/arrow-down.png">
</div>
</div>
<div class="eng-span">
English
Español
</div>
</div>
<div class="play-button">
<!-- if the array members has a property of video, then the play button will show on the slide. If not it will not show the image -->
<img v-if="member.hasOwnProperty('video')" #click="showContent" src="#/assets/play-button.png">
</div>
</div>
<!-- <MemberBioPage :member="member" v-on:showContent="showContent"/> -->
</slide>
</carousel>
<modal name="video-modal"
:width="1706"
:height="960">
<video width="1706" height="960" :src="(selectedList && selectedList[this.currentPage]) ? selectedList[this.currentPage].video : ''" autoplay />
</modal>
<div class="footer-controls">
<div class="footer-bar">
<p>Tap Back or Next to view additional profiles.</p>
<p>Tap the arrows to scroll text up or down.</p>
</div>
<div class="nav-container">
<img class="nav-bubble" src="#/assets/navigation-bubble-bio-page.png" alt="An image where the back, next and close button sit" />
</div>
<button class="close-button" #click="closeInfo">
<img src="#/assets/x-close-button.png" />
CLOSE
</button>
</div>
</div>
</template>
<script>
import { Carousel, Slide } from 'vue-carousel'
export default {
data () {
return {
currentPage: 0,
pageChangeHeight: -10,
scrollVar: 0
}
},
components: {
// MemberBioPage,
Carousel,
Slide
},
mounted () {
this.enableArrows()
},
updated () {
this.enableArrows()
},
computed: {
selectedList () {
return this.$store.state.selectedList
},
selectedListIndex () {
return this.$store.state.selectedListIndex
},
founderChairman () {
return this.$store.state.founderChairman
}
},
methods: {
enableArrows () {
var outerHeight
var innerHeight
if (document.querySelectorAll('.VueCarousel-slide-active').length > 0) {
outerHeight = document.querySelectorAll('.VueCarousel-slide-active .bio-container')[0].clientHeight
innerHeight = document.querySelectorAll('.VueCarousel-slide-active .inner-scroll')[0].clientHeight
} else {
outerHeight = document.querySelectorAll('.VueCarousel-slide .bio-container')[0].clientHeight
innerHeight = document.querySelectorAll('.VueCarousel-slide .inner-scroll')[0].clientHeight
}
this.pageChangeHeight = outerHeight - innerHeight
return this.pageChangeHeight
},
scrollUp () {
this.scrollVar += 40
console.log(this.scrollVar += 40)
},
scrollDown () {
this.scrollVar -= 40
console.log(this.scrollVar)
},
OnPageChange (newPageIndex) {
this.scrollVar = 0
this.currentPage = newPageIndex
this.pageChangeHeight = -10
},
closeInfo () {
if (this.$store.state.selectedList === this.$store.state.foundersList) {
this.$store.commit('setSelectedState', this.$store.state.foundersList)
this.$router.push({ name: 'Carousel' })
} else if (this.$store.state.selectedList === this.$store.state.chairmanList) {
this.$store.commit('setSelectedState', this.$store.state.chairmanList)
this.$router.push({ name: 'Carousel' })
}
},
showContent () {
this.$modal.show('video-modal')
},
toEnglish () {
this.scrollVar = 0
},
toSpanish () {
var spanishPos
if (document.querySelectorAll('.VueCarousel-slide-active').length > 0) {
spanishPos = document.querySelectorAll('.VueCarousel-slide-active .spanish')[0].offsetTop
} else {
spanishPos = document.querySelectorAll('.VueCarousel-slide .spanish')[0].offsetTop
}
this.scrollVar = -spanishPos
}
}
}
</script>
This is my store index file:
import Vue from 'vue'
import Vuex from 'vuex'
import chairmans from '#/data/chairmans-club'
import founders from '#/data/founders-circle'
import memoriam from '#/data/in-memoriam'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
finishedLoading: false,
transitioning: false,
foundersList: founders,
chairmanList: chairmans,
selectedList: founders,
selectedListIndex: -1,
founderChairman: true,
inMemoriam: memoriam,
idleTimer: {
id: null,
duration: 1
},
idleTimeoutModal: {
show: false,
duration: 1
}
},
mutations: {
setSelectedState (state, list) {
state.selectedList = list
},
setSelectedListIndex (state, idx) {
state.selectedListIndex = idx
},
showIdleTimeoutModal (state, value) {
state.idleTimeoutModal.show = value
},
founderChairmanClicked (state, data) {
state.founderChairman = data
},
setInMemoriam (state, content) {
state.inMemoriam = content
}
},
actions: {
restartIdleTimer ({ state, commit }) {
clearTimeout(state.idleTimer.id)
state.idleTimer.id = setTimeout(() => {
commit('showIdleTimeoutModal', true)
}, state.idleTimer.duration * 1000)
},
stopIdleTimer ({ state }) {
clearTimeout(state.idleTimer.id)
}
}
})
export default store
and an example of the data i am pulling:
const event = [
{
index: 0,
name: 'Member Name',
carouselImage: require('#/assets/carousel-images/image.jpg'),
drawerImage: require('#/assets/drawer-images/drawerimage.jpg'),
imgSrc: require('#/assets/bio-images/bioimage.jpg'),
quote: '“quote.”',
spanishQuote: `“spanish quote.”`,
bio: '<p>bio copy here</p>',
spanishBio: '<p>spanish bio copy</p>',
pin: require('#/assets/pin.png')
}
]
export default event

Masonry.js not applaying layout

I am using Laravel, VueJS and Masonry.js library to create a dynamic gallery and i'm in front of a weird problem.
I have this in my VueJS template:
<template lang="html">
<div id="uploads-grid">
<div class="grid" ref="grid">
<!-- GRID START -->
<div class="grid-item white-outline" v-for="(bg, index) in bgs" :style="'width:' + wd + 'px;height:' + hg[index] + 'px;'">
<img :src="'/storage/users/1/photos/1/' + bg.photo" :width="wd" :height="hg[index]">
</div>
<!-- GRID END -->
</div>
</div>
</template>
And this is how i get my images:
<script>
import Masonry from 'masonry-layout';
export default {
data() {
return {
bgs: [],
wd: '300',
hg: []
}
},
methods: {
getPhotos() {
var url = '/photos/get'
axios.get(url).then(r => {
this.bgs = r.data
for (var i = 0; i < r.data.length; i++) {
var factor = r.data[i].width / 300
var resizeHeight = Math.floor(r.data[i].height / factor)
this.hg.push(resizeHeight)
}
});
}
},
mounted() {
let $masonry = new Masonry(this.$refs.grid, {
itemSelector: '.grid-item',
columnWidth: 300,
isFitWidth: true
});
},
created() {
this.getPhotos()
}
}
</script>
The problem is that every image appears bellow the last one.
Either way, this other piece of code works just fine:
<template lang="html">
<div id="uploads-grid">
<div class="grid" ref="grid">
<!-- GRID START -->
<div class="grid-item white-outline" v-for="(bg, index) in bgs" :style="'width:' + wd[index] + 'px;height:' + hg[index] + 'px;'">
<img :src="bg">
</div>
<!-- GRID END -->
</div>
</div>
</template>
<script>
import Masonry from 'masonry-layout';
export default {
data() {
return {
bgs: [],
wd: [],
hg: []
}
},
methods: {
rndBg() {
for (var i = 0; i < 20; i++) {
var wd = 300
var hg = Math.floor(Math.random() * 350) + 150
var bgsrc = 'https://placehold.it/' + wd + 'x' + hg
this.bgs.push(bgsrc)
this.wd.push(wd)
this.hg.push(hg)
}
}
},
mounted() {
let $masonry = new Masonry(this.$refs.grid, {
itemSelector: '.grid-item',
columnWidth: 300,
isFitWidth: true
});
},
created() {
this.rndBg()
}
}
</script>
The problem is that i'm using placeholdit dummy images and not the ones i want so... I does not works for me. I'm using the same logic but... yes, i can't get it to work.
Main point: You gotta trigger Masonry again after adding new image.
Masonry works by setting fixed position, width, etc... to the elements. So everytime something is added/removed, you should trigger Masonry to recalculate and set again the fixed position, width, etc...
e.g. https://codesandbox.io/s/4xw830q1r4 components/Hello.vue
<template>
<div class="hello">
<button #click="addImg"> add img </button>
<br/><br/>
<div class="grid">
<div class="grid-item" v-for="img in imgs">
<img :src="img">
</div>
</div>
</div>
</template>
<script>
import Masonry from 'masonry-layout';
export default {
name: 'hello',
data () {
return {
imgs:[
`https://unsplash.it/200/300/?random=${Math.random()}`,
`https://unsplash.it/200/100/?random=${Math.random()}`,
`https://unsplash.it/200/400/?random=${Math.random()}`,
`https://unsplash.it/200/250/?random=${Math.random()}`
]
}
},
mounted() {
this.triggerMasonry();
},
methods:{
triggerMasonry() {
console.log('triggerMasonry');
var grid = document.querySelector('.grid');
var msnry = new Masonry( grid, {
itemSelector: '.grid-item',
columnWidth: 200
});
},
addImg(){
this.imgs.push(`https://unsplash.it/200/300/?random=${Math.random()}`);
this.$nextTick(()=>{
this.triggerMasonry();
});
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.grid {
width: 100%;
img {
width: 100%;
}
}
</style>
triggerMasonry on mounted, and also triggerMasonry after a new image is added.
You will also need to use nextTick to triggerMasonry only after the image has been added to DOM.

Categories