Vue.js Scroll Snap to Component - vue.js

I'm trying to scroll snap to components in Vue.js 3. I can get scroll snap to work correctly with vanilla HTML and CSS. Here's what it looks like: Scroll Snap Demo
I'm trying to copy that simple layout but using App.js as the container and components as the divs. Here's what it looks like in App.vue:
<template class="container">
<CompI class="snapAlign"/>
<CompII class="snapAlign"/>
<CompIII class="snapAlign"/>
<CompIV class="snapAlign"/>
</template>
<script>
import CompI from './components/CompI.vue'
import CompII from './components/CompII.vue'
import CompIII from './components/CompIII.vue'
import CompIV from './components/CompIV.vue'
export default {
name: 'App',
components: {
CompI,
CompII,
CompIII,
CompIV
}
}
</script>
<style>
*{
margin: 0;
padding: 0;
text-align: center;
}
.container{
overflow-y: scroll;
scroll-snap-type: y mandatory;
}
.snapAlign{
scroll-snap-align: start;
}
</style>
Here's what I've got for components, they're all basically the same as each other:
<template>
<div class="one">
<h1>One</h1>
</div>
</template>
<script>
export default {
name: 'CompI'
}
</script>
<style scoped>
.one{
height: 100vh;
width: 100vw;
background-color: green;
}
</style>
I also tried using vue-scroll-snap but it doesn't seem to work on components the way the guides show it working on divs. This was the guide I tried: Vue Scroll Snap Guide
Here's what I've got for App.vue using that method:
<template>
<vue-scroll-snap >
<CompI/>
<CompII/>
<CompIII/>
<CompIV/>
</vue-scroll-snap>
</template>
<script>
import CompI from './components/CompI.vue'
import CompII from './components/CompII.vue'
import CompIII from './components/CompIII.vue'
import CompIV from './components/CompIV.vue'
import VueScrollSnap from "vue-scroll-snap"
export default {
name: 'App',
components: {
CompI,
CompII,
CompIII,
CompIV,
VueScrollSnap
}
}
</script>
<style>
*{
margin: 0;
padding: 0;
text-align: center;
}
</style>
The components are the same as the other method.
The pages appear correctly but there's no scroll snap effect with either of these attempts. I'd like to try and use the components as full screen elements to snap to, the app will scale better like that.
+++ Solution +++
Ivo Gelov was correct, remove the class from <template>, place the components inside a div and give it the class "container". For some reason that alone wasn't enough, more style was needed on that class then it works. Here's the final CSS for that class:
.container{
width: 100%;
height: 100vh;
margin: 0 auto;
overflow-x: hidden;
overflow-y: scroll;
scroll-snap-type: y mandatory;
}

Related

Rendering nested components

I am starting to learn VueJS by building a simple website.
Right now I have made three components:
Header
Navigation
Topbar
I want to render navigation and topbar inside header, so I can call the header component inside every page (I haven't found a way to make a "layout" or something, that every page uses).
Header.vue
<template>
<header id="topNav">
<topbar />
<navigation />
</header>
</template>
<script lang="ts">
import Vue from 'vue'
import navigation from '../navigation/navigation.vue'
import Topbar from '../topbar/topbar.vue'
export default Vue.extend({
components: { Topbar, navigation },
})
</script>
<style scoped>
#topNav {
background-color: #fff;
-webkit-box-shadow: 0 0 35px 0 rgb(154 161 171 / 15%);
box-shadow: 0 0 35px 0 rgb(154 161 171 / 15%);
position: fixed;
left: 0;
right: 0;
top: 0;
z-index: 1001;
padding: 0 12px;
}
</style>
Topbar
<template>
<div id="topBar">
<h2>this is the topbar</h2>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({})
</script>
<style scoped>
#topBar {
border-bottom: 1px solid #dee2e6;
height: 70px;
padding: 0 10px;
z-index: 100;
position: fixed;
left: 0;
right: 0;
}
</style>
And when I call it on for example my home page (index.vue) nothing renders.
<template>
<main>
<header />
<div id="page-content">
<h1>Home</h1>
</div>
</main>
</template>
<script lang="ts">
import Vue from 'vue'
import Header from '../components/header/header.vue'
export default Vue.extend({
name: 'Home',
components: { Header },
})
</script>
I've tried reading the documentation and search around, but haven't been able to figure out what I'm doing wrong.
Since the question is tagged with a Nuxt tag, I'll recommend looking into Nuxt layouts: https://nuxtjs.org/docs/concepts/views#layouts
There is a default named default that you could use by creating the file /layouts/default.vue and passing the components inside of it.
You can of course change that with a layout: 'yolo' if you want another 'yolo` layout.
Pro tip: you don't need to import the components yourself in Nuxt.
You have named your component header, which is a standard html element. Therefore the browser will probably just try to render a standard <header> element instead of your component.
Therefore it is advised to always use multi word component names. See docs here. You can use eslint in your code editor to help you spot these mistakes.
PS: if you are learning vue from the start, I would advise you to use the composition api with the script setup approach, as it makes things easier and provide the opportunity to write clearer code as components grow.

How to disable Vuetify's style?

I want to parse markdown to html and use syntax highlighting.
My SFC is as follows:
<template>
<div v-html="html"></div>
</template>
<script>
import marked from 'marked'
import hljs from 'highlightjs';
export default {
name:"Article",
props:['md'],
computed:{
html(){
return marked(this.md)
}
},
created: function () {
marked.setOptions({
langPrefix: '',
highlight: function(code, lang) {
return hljs.highlightAuto(code, [lang]).value
}
})
},
}
</script>
<style src='highlightjs/styles/github-gist.css'></style>
The resulting code blocks are look like this:
This is Vuetify's style.
https://vuetifyjs.com/en/styles/content/#code
I want to disable or override it.
The following code does not work for code blocks:
<style scoped>
.v-application code {
background-color: unset !important;
color: unset !important;
box-shadow: unset !important;
}
.myclass {
color:red !important;
}
</style>
Result:
Vuetify has the following CSS specified for the code tags:
.v-application code {
background-color: #f5f5f5;
color: #bd4147;
box-shadow: 0 2px 1px -1px rgba(0,0,0,.2),
0 1px 1px 0 rgba(0,0,0,.14),
0 1px 3px 0 rgba(0,0,0,.12);
}
You can see this if you open developer tools and inspect a code tag on their website.
Either override those values to your own, or just set them all to unset or unset !important. For example:
.v-application code {
all: unset;
color: #eee
}
/* Or with increased specificity */
.v-application code.code--custom {
all: unset;
color: #eee
}
Actualy the style override you suffer from wouldn't be a problem if you just import your HighlightJS CSS directly after Vuetify in your main.js.
//main.js
import Vue from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify';
import '<your_path>/highlight.min.css'
Consider also using a Vue Directive for global usage.
//main.js
Vue.directive('highlightjs', {
deep: true,
bind: function(el, binding) {
// highlight all targets
let targets = el.querySelectorAll('code')
targets.forEach((target) => {
// override this in case of binding
if (binding.value) {
target.textContent = binding.value
}
hljs.highlightBlock(target)
})
},
})
Then you can simply use it like this:
<pre v-highlightjs>
<code class="javascript">
// your code goes here //
</code>
</pre>
I made a JSFIDDLE for this, which is a modified version of a vue HighlightJS example by Chris Hager.
https://jsfiddle.net/b8jontzr/2/

VueJs: How to change CSS background image for each page route?

I am probably making this more complicated than it needs to be, but does anyone have any ideas on how I can swap out custom banner images for each of my pages (when route changes to any thing other than index page)? I have the slots working , but need way to change background image in the non-index banners. Here's my code:
App.vue and shows only the main page banner:
<template>
<div id="app">
<BannerHome v-if="isHomeView" />
<main id="routerView">
<RouterView :key="$route.fullPath" />
</main>
<Footer />
</div>
</template>
<script>
import BannerHome from '#/components/BannerHome.vue'
import Footer from '#/components/Footer.vue'
export default {
name: 'App',
components: {
BannerHome,
Footer
},
computed: {
isHomeView() {
return this.$route.fullPath === '/'
}
}
}
</script>
Example of non-index page (About.vue):
<template>
<article>
<banner>
<template v-slot:titleTop>Mission Focused</template>
<template v-slot:titleBottom>
Dedicated To Service
</template>
</banner>
<section class="container-md pb-5">
<h1>About Page</h1>
</section>
</article>
</template>
<script>
import Banner from '#/components/Banner.vue'
export default {
name: 'About',
components: {
Banner
}
}
</script>
Banner component for non-index pages (Banner.vue). Note the background image. How can I swap this out for each non-index page? Right now the background image is shown for ALL non-index pages.
<template>
<header>
<MainNav />
<div class="container">
<h1><em><slot name="titleTop"></slot><br />
<span class="pl-3"><slot name="titleBottom"></slot></span></em>
</h1>
</div>
</header>
</template>
<script>
import MainNav from '#/components/MainNav.vue'
export default {
name: 'Banner',
components: {
MainNav
}
}
</script>
<style lang="scss" scoped>
header {
position: relative;
width: 100%;
// set this page height here
height: 20em;
text-transform: uppercase;
}
header::before {
content: "";
display: block;
position: absolute;
left: 0;
bottom: 6em;
width: 100%;
// set this page height here
height: calc(20em + 10em);
z-index: -1;
transform: skewY(-3.5deg);
background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)),
url('../assets/mockphoto.jpg') < -----how to swap out this image?
no-repeat left top,
linear-gradient(#4e4376, #2b5876);
background-size: cover;
border-bottom: 0.2em solid #fff;
}
h1 {
font-size: 1.75rem!important;
font-weight: 700!important;
letter-spacing: 0.01em;
padding: 2.5rem 0 0 0;
// text-shadow: 0.022em 0.022em 0.022em #111;
color: #fff;
}
</style>
Btw, I am also using Gridsome...so if there is also an easy way to access page data via GraphQL for this purpose I can also use that. Thanks for any help !
So, I actually just need to add the /deep/ keyword to the parent CSS like so:
/deep/ header::before {
background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)),
url('../assets/mockphoto.jpg')
no-repeat left top,
linear-gradient(#4e4376, #2b5876);
}
Docs: https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors

Unable to import multiple component in vuejs with Typescript

I have two component I wanted to use both on some other component as follow
import { Component, Vue } from 'vue-property-decorator'
import DDGDriver from '#/components/ddg-driver.vue'
import OverLay from '#/components/overlay.vue'
#Component({
components: {
'over-Lay':OverLay, 'DDGDriver':DDGDriver
}
})
export default class DDGPage extends Vue {
showModal = true;
onModalClose(){
this.showModal = false;
}
}
but its not working only second imported component is getting placed instead of first, I don't know why this is happening.
<template>
<div>
<over-Lay v-on:close="onModalClose()" v-if="showModal">
<DDGDriver />
</over-Lay>
</div>
</template>
DDGDriver component
<template>
<div class="driver">
<h1>this is ddg driver</h1>
</div>
</template>
<script>
import Vue from 'vue';
export default class DDGDriver extends Vue {
}
</script>
<style lang="less" scoped>
.driver{
height:auto;
width: 400px;
background-color: #FFF;
}
</style>
OverLay component
<template>
<div class="overlay">
<div class="containt">
<div #click="removeModal()" class="close">X</div>
<slot>
This will only be displayed if there is no content
to be display.
</slot>
</div>
</div>
</template>
<script>
import Vue from "vue";
export default class OverLay extends Vue {
removeModal(){
this.$emit('close');
}
}
</script>
<style lang="less" scoped>
.overlay {
position: fixed;
bottom: 0px;
right: 0px;
left: 0px;
top: 0px;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
.containt {
min-width: 20px;
height: auto;
background-color: #fff;
padding: 15px;
.close {
position: relative;
top: -32px;
right: -27px;
float: right;
height: 25px;
width: 25px;
background: #fff;
border-radius: 50%;
}
}
}
</style>
You forgot to add #Component({}) in your children components.
<script>
import Vue from 'vue';
#Component({})
export default class DDGDriver extends Vue {}
</script>

Not able to call Vue component in vue cli application

I am trying to create the component for tile which can be used in multiple views. I have called the component in view but it is not working
Code of component (Tile.vue)
<template>
<div class="tile">
<label class="title">Tile Title</label>
</div>
</template>
<script>
export default {
name: 'CustomTile'
}
</script>
<style scoped lang="less">
.tile { width:248px;
height: 126px;
background-color:#e4e4e4;
.title {
padding-left: 15px;
}
}
</style>
code of view (Report.vue) where I am trying to call above component
<template>
<div>
<div class="topnav">
<button type="button">Expand/Collapse</button>
</div>
<div class="content">
<CustomTile></CustomTile>
</div>
</div>
</template>
<script>
import CustomTile from '#/components/Tile.vue'
export default {
name: 'Report'
}
</script>
<style scoped lang="less">
.topnav {
overflow: hidden;
background-color: #d6d6d6;
}
.topnav a {
float: left;
color: #f2f2f2;
text-align: center;
padding: 14px 16px;
text-decoration: none;
font-size: 17px;
}
.topnav a:hover {
background-color: #ddd;
color: black;
}.topnav a.active {
background-color: #4CAF50;
color: white;
}
.content {
height:500px;
width:500px;
}
</style>
CustomTile is not getting rendered. I am not able to figure out what/where is the problem.
You need to properly import the component in the parent where you want to use it and register it:
Report.vue:
<script>
import CustomTile from '#/components/Tile.vue'
export default {
name: 'Report',
components: {
CustomTile
}
}
</script>
And then, since CustomTile is a CamelCase component name, you need to use the following notation:
<custom-tile></custom-tile>