Lozad lazy load with v-for in Vue.js - javascript

I'm using lozad.js for lazy loading my images. In my vue.js project I have
made a custom component that's globally registered it looks basically like this:
<template>
<img :alt="alt" :data-src="lazySrc" />
</template>
<script>
import lozad from 'lozad';
export default {
props: {
lazySrc: {
type: String,
default: null,
},
alt: {
type: String,
default: ''
}
},
mounted() {
const observer = lozad(this.$el);
observer.observe();
},
};
</script>
When I try this in a .html file:
#foreach($recommendedFriends as $friend)
<a href="{{ route('profile.show', $friend->slug) }}">
<lozad class="lozad bg-cover bg-gray-200 border-4 border-white w-20 h-20 rounded-full shadow-lg z-50 m-2 zoom"
lazy-src="{{ $friend->thumb_image }}">
</lozad>
</a>
#endforeach
The images are being lazy loaded and it works great! But when I try it in a .vue file:
<div v-for="breed in breeds">
<lozad class="lozad rounded-lg"
:lazy-src="breed.full_overview_image">
</lozad>
</div>
Nothing is being lazy loaded. I don't get an error and the images do show up but they are not lazy loaded.
How can I fix this? Am I missing something?

Related

V-model on div tag component

There's an issue when using v-model to div tags. Apparently, div tags doesn't allow v-model and I've decided to create my comment section as a component. I needed to assign this div text area as is because of UI/UX reasons. textarea, input, etc tags, as to my knowledge, these tags are not compatible with contenteditable="true"; I need to expand the height of the input field as a user types in their comments. Below is the vue component that I imported in my parent view.
<!-- CommentSection.vue -->
<template>
<div id="chatId" contenteditable="true" placeholder="Leave a message" class="overflow-hidden block mx-4 text-left p-2.5 w-full text-sm text-gray-900 bg-white rounded-2xl border border-gray-300 focus:ring-blue-500 focus:border-blue-500"/>
</template>
<style>
#chatId[contenteditable="true"]:empty:not(:focus):before {
content: attr(placeholder)
}
</style>
On my view file, I imported it and used v-model into it, just like this.
<!--MainPage.vue-->
<template>
...
...
<CommentSection v-model="comment"/>
<button #click="submitPost()"> Submit </button>
...
...
...
</template>
<script>
import CommentSection from '#/components/CommentSection.vue'
export default{
name: 'MainPage',
data(){
return{
comment: '',
}
},
components: { CommentSection },
methods:{
submitPost(){
console.log(this.comment);
},
},
}
</script>
However, when I check my console, it gives me the value "null" or just nothing. Is there's a way to fix this? Or is it the way I implemented it that causes the problem.
EDIT: Here's the running code in codesandbox.
I solved your problem and the code is as follows. I hope I have helped
By adding # to the div tag, we can see the changes in the content of the tag in the change method. And in that method, use emit$ to share its value with other components
<!-- CommentSection.vue -->
<template>
<div id="chatId" #input="chanage" contenteditable="true" placeholder="Leave a message" class="overflow-hidden block mx-4 text-left p-2.5 w-full text-sm text-gray-900 bg-white rounded-2xl border border-gray-300 focus:ring-blue-500 focus:border-blue-500"/>
</template>
<script>
export default {
methods: {
chanage (e) {
this.$emit('value-div', e.target.textContent)
}
}
}
</script>
<style>
#chatId[contenteditable="true"]:empty:not(:focus):before {
content: attr(placeholder)
}
</style>
And here we have the props created by $emit, whose value we initialize in the comment variable. Actually, it has a function similar to v-model.
<!--MainPage.vue-->
<template>
...
...
<CommentSection #value-div="(value)=>comment = value"/>
<button #click="submitPost()"> Submit </button>
...
...
...
</template>
<script>
import CommentSection from '#/components/CommentSection.vue'
export default{
name: 'MainPage',
data(){
return{
comment: '',
}
},
components: { CommentSection },
methods:{
submitPost(){
console.log(this.comment);
},
},
}
</script>

Nuxt 3 Images not rendered when set the src dynamically on build process

:I have the following problem with a Nuxt3 application.
When set an image source via template strings the build process will not render the images.
Otherwise it will, when i set the image src normally. But i need it dynamically.
There are teasers with different images that need to be rendered.
Everythung is working fine, e.g. props ...
The working code:
...
<img
src="/assets/_DSC0238_E.jpg"
:alt="props.name"
class="w-full aspect-square object-cover"
:class="`aspect-${props.aspectRatio}`"
/>
...
The not working code:
...
<img
:src="`props.image`"
:alt="props.name"
class="w-full aspect-square object-cover"
:class="`aspect-${props.aspectRatio}`"
/>
...
What is the way to solve this issue?
In case you are using Nuxt 3 with Vite as Bundler
Set Assets composable.
export default function useAssets() {
const svgs = import.meta.globEager('/src/assets/*.svg');
const pngs = import.meta.globEager('/src/assets/*.png');
const jpegs = import.meta.globEager('/src/assets/*.jpeg');
return {
aboutImage: svgs['/src/assets/aboutImage.svg'].default,
search: svgs['/src/assets/search.svg'].default,
info: pngs['/src/assets/info.png'].default,
};
}
Then in any file:
<template>
<div>
<img :src="assets.info">
</div>
</template>
<script lang="ts">
import { defineComponent } from '#vue/runtime-core';
import useAssets from '../composable/useAssets';
export default defineComponent({
setup() {
const assets = useAssets();
return {
assets,
};
},
});
</script>
resource
In case (Vu3, Vue2, Nuxt 2) and bundler is Webpack
You need to require the image path, and set a dynamic src attribute by adding a colon before :src
<img
:src="require(`~/assets/${props.image}`)"
:alt="props.name"
class="w-full aspect-square object-cover"
:class="`aspect-${props.aspectRatio}`"
/>
In case (Vu3, Vue2, Nuxt 2) and bundler is Vite
const getImage = async imgName => {
// set the relative path to assets
const module = await import(/* #vite-ignore */ `../../assets/${imagName}`)
return module.default.replace(/^\/#fs/, '')
}
<img
:src="getImage(props.image)"
:alt="props.name"
class="w-full aspect-square object-cover"
:class="`aspect-${props.aspectRatio}`"
/>
we can use v-bind to assign them a string value dynamically. read this
Try like this:
<img
v-bind:src="require(`#/assets/${props.image}`)"
:alt="props.name"
class="w-full aspect-square object-cover"
:class="`aspect-${props.aspectRatio}`"
/>
or for shorthand syntax
<img
:src="require(`#/assets/${props.image}`)"
:alt="props.name"
class="w-full aspect-square object-cover"
:class="`aspect-${props.aspectRatio}`"
/>

Why my picture does not appear, seeing only the alt?

I am trying to display a pic inside a card but it does not work... actually I only see instead of the pic the attribute alt but I don't see the pic...
Here is my code :
import React, {Component} from "react";
import classes from "./cards.css"
const Cards = (props) => {
return (
<>
<div id="card" className={"card text-white bg-info mb-3"}
style={{maxWidth: 200, marginRight: 10}}>
<div className="card-header">Test</div>
<img src="myPicture.jpg" className="card-img-top" alt="pic" />
<div className="card-body">
<h4 className="card-title">This is a test</h4>
</div>
</div>
</>
)
}
export default Cards;
Do you know how can I do to see my pic on my card ?
Thank you a lot for your help !
Have you checked that the image has loaded correctly? You can use the inspector 'network' tab. Make sure the image is being loaded by the page. It will likely be red if it is not found (in the inspector).
If you're using webpack, try this:
import backgroundImage from './myPicture.jpg';
// all other code
return (
<div>
<img src={backgroundImage} alt="Background image" className="image" />
</div>
)
Config required in webpack:
module.exports = {
// other config
module: {
rules: [
// other rules
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader'
},
]
}
}
You need to install file-loader package
Add this
import BackgroundImage from './myPicture.jpg';
<img src="{require('./myPicture.jpg')}" className="card-img-top"alt="pic" />

Scroll down when clicked with Vue.js

What I want to do is that when the user clicks on an article, it scrolls down to a sibling section. Code looks something like this.
<template>
<article #click="handleCardClick" class="text-center mr-8 mb-12 cursor-pointer hover:opacity-50 w-1/5">
<picture class="flex justify-center items-center mb-4 w-full" style="height: 320px">
<img :src="source" :alt="imgAlt" class="shadow-md" style="" />
</picture>
<h4 class="font-bold mb-1">{{ title }}</h4>
<h6 class="text-sm text-gray-600">{{ tags.length > 0 ? tags[0].name : '' }}</h6>
</article>
</template>
<script>
import { mapActions, mapState } from 'vuex';
export default {
props: {
title: {
type: String,
required: true,
},
},
computed: {
...mapState({
previewIndex: state => state.templates.hasTemplate
}),
},
methods: {
...mapActions({
setActiveTemplate: 'templates/setActive',
setPreview: 'templates/setPreview',
removePreview: 'templates/removePreview',
}),
handleCardClick () {
this.setActiveTemplate(this.template);
this.selectTemplate(this.pos);
},
}
}
</script>
And the other file looks like this
<template>
<section v-if="template" class="flex justify-between w-full pt-10 pl-10 pr-5 pb-12 relative border-b border-t border-black my-4" style="height: 75vh">
<article class="flex flex-col justify-between" style="width: 25%">
<button #click="changeSection('invite')" class="h-1/3 pb-4">
<picture class="h-full w-full flex justify-center items-center bg-gray-100">
<img :src="template.url || ''" class="bg-gray-200 shadow-lg" style="min-height: 20px; min-width: 20px; height:80%" alt="Preview de la invitacion">
</picture>
</button>
</article>
</section>
</template>
I'm a bit new to Vue, so maybe it's really simple and I just can't find how to do it :) Thanks in advance!
You only need to assign a reference ref to each article and then build a method to go to any of your referenced articles:
<article #click="goto('art1')">Go to article 1</article>
For earch sibiling declare it's reference so you can call them on the goto method
<article ref="art1">
Article 1
</article>
Declare the goto method, it has a parameter, the reference of where you want to go.
methods: {
goto(refName) {
var element = this.$refs[refName];
var top = element.offsetTop;
window.scrollTo(0, top);
}
},
And this is it.
If you have the click action inside a child component then you'll have to use $emit to perform the click action on the parent, here is an example following the above:
Parent
<template>
<Article #scrollto="goto"></Article>
<Section ref="art1">
...
</Section>
</template>
<script>
import Article from "./article";
import Section from "./section";
export default {
methods: {
goto(refName) {
var element = this.$refs[refName];
var top = element.offsetTop;
window.scrollTo(0, top);
}
}
}
</script>
Article.vue
<template>
<div>
<button #click="$emit("scrollto", 'art1')">
Go to the Article!
</button>
</div>
</template>
Documentation about vue ref function
Documentation about window.scrollTo function

How do I dynamically show a mobile menu with Tailwind and Vue.js?

I'm trying to build a responsive menu with Tailwind CSS and Vue.js. Currently I have this template:
<template>
<nav class="flex items-center justify-between flex-wrap bg-pink-100 p-6">
<div class="flex items-center flex-shrink-0 mr-6">
<span class="font-semibold text-xl tracking-tight">Pixie</span>
</div>
<div class="block md:hidden" >
<button #click='clickMenu' class="flex items-center px-3 py-2 border rounded" >
<svg class="fill-current h-3 w-3" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><title>Menu</title><path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"/></svg>
</button>
</div>
<div class="w-full flex-grow md:flex md:items-center md:w-auto" v-if="menuVisible">
<div class="text-sm md:flex-grow">
<a href="#responsive-header" class="block mt-4 md:inline-block md:mt-0 hover:text-white mr-4">
Features
</a>
<a href="#responsive-header" class="block mt-4 md:inline-block md:mt-0 hover:text-white mr-4">
Pricing
</a>
<a href="#responsive-header" class="block mt-4 md:inline-block md:mt-0 hover:text-white">
Blog
</a>
</div>
<div>
Sign Up
Log In
</div>
</div>
</nav>
</template>
With this Javascript:
<script>
export default {
data: function() {
return {
menuVisible: true
}
},
methods: {
clickMenu: function() {
this.menuVisible = !this.menuVisible
}
}
}
</script>
All I want to do is initially hide the mobile menu when the breakpoint reaches 'sm' on Tailwind. This would mean the user would have to click the menu button to see the menu, which I think is the expected behavior on mobile devices.
I don't want to build 2 separate menus which get shown on different breakpoints as I want to avoid duplicating code. Is there a way to access the current breakpoint for Tailwind in Vue.js? This would mean I could set the menuVisible to a computed property which only allows it to be visible if the breakpoint is desktop or tablet, or if the user has clicked the menu.
Or is there another better way to do this?
Thanks for any help!
you can write a plugin for it in you app and import it, im using nuxt and this worked for me
export default (context, inject) => {
const burger = () => {
const menu = document.querySelector("#menu");
if (menu.classList.contains("hidden")) {
menu.classList.remove("hidden");
} else {
menu.classList.add("hidden");
}
};
inject("burger", burger);
context.$burger = burger;
};
One way of achieving this could be to configure the TailwindCSS-breakpoints in your tailwind.config.js and to then reuse that file to import the breakpoint-values into your Menu-component.
Here we are setting TailwindCSS breakpoints according to the TailwindCSS documentation. We are actually just setting the default TailwindCSS breakpoint values, but setting them makes them accessible via the file.
//tailwind.config.js
module.exports = {
theme: {
screens: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px'
}
}
}
Now, in your Menu.vue, you can import the breakpoint from your TailwindCSS-config and write the necessary function, to check if the current window-size is smaller than the md-breakpoint. If it's not, you can simply return true. If it is, you can check, if the menu was toggled open.
// Menu.vue
<script>
const tailwindConfig = require('tailwind.config.js')
export default {
data() {
return {
windowWidth: 0,
menuOpen: false,
mdBreakpoint: Number(tailwindConfig.theme.screens.md.replace('px', ''))
}
},
computed: {
menuVisible() {
return this.windowWidth > mdBreakpoint ? true : this.menuOpen
}
},
methods: {
updateWindowSize() {
this.windowWidth = window.innerWidth
},
clickMenu() {
this.menuOpen = !this.menuOpen
}
},
mounted() {
this.updateWindowSize()
window.addEventListener('resize', this.updateWindowSize)
},
beforeDestroyed() {
window.removeEventListener('resize', this.updateWindowSize)
}
}
</script>

Categories

Resources