SwiperJS lazy loading - Image not displaying when using data-src as the guide says - swiper.js

I have been struggeling trying to implement SwiperJS to my Statamic 3 project.
I have a working carousel/slider that when not using data-src and lazy loading works perfectly fine. But as soon as I try to implement lazy loading following the guide on their website. I get either a white image/background with an infinite loader or a white image/background and no loader.
here is my code:
HTML (the images come from a antlers foreach):
<div class="flex flex-col w-1/3 p-2">
<div class="h-full w-full swiper-container">
<div class="h-48 swiper-wrapper">
{{ foreach:photos }}
<div class="swiper-slide">
<img data-src="{{ value:url }}" class="w-full h-full object-cover object-center swiper-lazy">
<div class="swiper-lazy-preloader"></div>
</div>
{{ /foreach:photos}}
</div>
<div class="swiper-pagination"></div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
</div>
My JS:
// core version + navigation, pagination modules:
import Swiper, { Navigation, Pagination } from 'swiper';
import 'swiper/swiper-bundle.css';
// configure Swiper to use modules
Swiper.use([Navigation, Pagination]);
var mySwiper = new Swiper('.swiper-container', {
slidesPerView: 1,
spaceBetween: 0,
slidesPerGroup: 1,
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
pagination: {
el: '.swiper-pagination'
},
mousewheel: false,
keyboard: true,
loop: true,
parallax: true,
grabCursor: true,
// Disable preloading of all images
preloadImages: false,
// Enable lazy loading
lazy: {
loadPrevNext: true,
},
});
Current result:
Anyone know why my images are not being rendered?
Kind regards,
Robbert

You need to import Lazy module.
Change
import Swiper, { Navigation, Pagination } from 'swiper';
to
import Swiper, { Navigation, Pagination, Lazy } from 'swiper';
and change
Swiper.use([Navigation, Pagination]);
to
Swiper.use([Navigation, Pagination, Lazy]);

Related

How to make transition opacity work when element is removed?

Hi I made the following notification component for my vue app where I am looping through errors and success messages from vuex store. I am removing them after 3 seconds from the array. However this means the transition does not gets applied since the element gets removed from the DOM. How can I make that work? Please help me.
<template>
<div
id="toast-container"
class="fixed z-50 top-20 right-3"
>
<div
v-for="(error, index) in errors"
:key="error+index"
:class="`${error ? 'opacity-1 visible' : 'opacity-0 invisible'}
toast toast-error flex items-center transition-opacity`"
>
<img
svg-inline
src="#/assets/icons/alert_triangle.svg"
alt="alert icon"
>
<div class="pl-2">
<div class="toast-title">
Der er sket en fejl!
</div>
<div class="toast-message">
{{ error }}
</div>
</div>
</div>
<div
v-for="(message, index) in successMessages"
:key="message+index"
:class="`${message ? 'opacity-1 visible' : 'opacity-0 invisible'}
toast toast-success flex items-center`"
>
<img
svg-inline
src="#/assets/icons/shield_check.svg"
alt="alert icon"
>
<div class="pl-2">
<div class="toast-title">
Succes
</div>
<div class="toast-message">
{{ message }}
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Notifications',
computed: {
errors() {
return this.$store.state.global.errors
},
successMessages() {
return this.$store.state.global.successMessages
},
},
watch: {
errors: {
handler() {
setTimeout(() => {
this.removeError(0)
}, 3000)
},
deep: true,
},
successMessages: {
handler() {
setTimeout(() => {
this.removeSuccessMessage(0)
}, 3000)
},
deep: true,
},
},
methods: {
removeError(index) {
this.$store.commit('removeError', index)
},
removeSuccessMessage(index) {
this.$store.commit('removeSuccessMessage', index)
},
},
}
</script>
Have a look at https://vuejs.org/guide/built-ins/transition-group.html which is designed for this exact use case. Basically wrapping the whole v-for block with <TransitionGroup> and defining proper CSS classes is all you need to do, <TransitionGroup> will take care of animating the element and removal from DOM after animation is done, you just need to add/remove items from state.

Owl carousel not working with loop in vuejs and axios

I am using vue in my existing codeigniter project by including via CDN.
i am having problem in using owl carousel. i fetched data using axios and populated in div using v-for method. but it seems owl carousel not working.
I have also added screenshot of what i am facing.
here is the script part
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
new Vue({
el: '#offerstab',
data () {
return {
touroffers: [],
caroffers: [],
hoteloffers: [],
cruiseoffers: [],
flightoffers: [],
}
},
methods: {
getOffers () {
axios
.get('https://www.happyvoyaging.com/api/offers/listcustom',{
params: {
category: ['tours','flight','cars','cruise','rail','hotel']
}
})
.then(res => (this.touroffers = res.data.off_tours,this.caroffers = res.data.off_cars,this.hoteloffers = res.data.off_hotels,this.cruiseoffers = res.data.off_cruises,this.flightoffers = res.data.off_flights))
},
},
mounted() {
this.getOffers();
},
})
</script>
this is my html part where i want owl carousel to be initiated.
<div id="offerstab">
<div class="owl-carousel owl-carousel-nav-dark" data-items="3" data-loop="true" data-nav="true">
<div v-for="touroffer in touroffers" class="theme-inline-slider-item">
<div class="banner _h-33vh _br-5 banner-">
<div class="banner-bg" v-bind:style="`background-image:url('${touroffer.thumbnail}');`"></div>
<div class="banner-mask banner-mask-half"></div>
<a class="banner-link" href="#"></a>
<div class="banner-caption _ta-c banner-caption-bottom">
<h5 class="banner-title _tt-uc">{{ touroffer.title }}5</h5>
<p class="banner-subtitle">Top deals from most visited cities</p>
</div>
</div>
</div>
</div>
</div>
this is how its looking, it should show 3 columns here but it is showing one below another
This is what i expect, as it works well with static code without vue
This is data in touroffers after axioscall

Init Vue Awesome swiper on product card hover

ideally I need vue awesome swiper to init on product card hover, so until the user hovers, catalog loads only one img per product instead of multiple.
HTML
<div
#mouseover="handleMouseOver"
#mouseleave="handleMouseLeave"
/>
<div
v-if="media.length > 1"
ref="swiper"
v-swiper:mySwiper="swiperOption"
>
<div class="swiper-wrapper">
<div
v-for="image in images"
:key="image.id"
class="swiper-slide image__wrapper"
>
<img
:src="image.attributes.src"
:width="imgWidth"
:height="imgHeight"
:alt="imgAlt"
>
</div>
</div>
</div>
</div>
Component
data: () => ({
swiperOption: {
loop: true,
slidesPerView: 1,
centeredSlides: true,
spaceBetween: 30,
},
}),
methods: {
slideStart() {
if (this.$refs.swiper) {
this.mySwiper.activeIndex = 2;
this.mySwiper.autoplay.start();
}
},
slideStop() {
if (this.$refs.swiper) {
this.mySwiper.autoplay.stop();
this.mySwiper.activeIndex = 1;
}
},
handleMouseOver() {
this.isHovered = true;
this.slideStart();
},
handleMouseLeave() {
this.isHovered = false;
this.slideStop();
},
},
What I've tried and what problems encountered:
At first, I've added isHovered condition to v-if and used element in v-else, however after hover swiper refuses to autoplay (but reacts on activeIndex change)
After that I've tried adding init:false to swiperOption and this.$mySwiper.init() on hover, but it crashes whenever I'm trying to leave the page:
Would appreciate any ideas.
Solved by creating a parent div, and moving 'v-if' logic with hover condition there.

v-for delay dom patching when data update

I'm noticing that the v-for I'm using to render some images inside a component, when data are updated using the event bus,I will have a little delay in DOM content replacing. Is there a way to solve this little problem?
NB: I can't replicate the data passed because it's a 50 elements list of images urls and are provided by an external api service.
<template>
<div class="row m-0 pl-5 pr-5">
<div class="col-4 col-img hide p-0" v-for="(img, idx) in feed" :key="idx" v-observe-visibility="visibilityChanged">
<img class="img-fluid w-100 h-100 ig-image" :src="img.url">
<div class="card-img-overlay text-center">
<p class="text-white">{{ img.likes }} <i class="fas fa-heart"></i> {{ img.comments }} <i class="fas fa-comment"></i></p>
</div>
<a class="stretched-link" href="#image-modal" data-toggle="modal" v-on:click.prevent="zoomImage(img.url)"></a>
</div>
</div>
</template>
<script>
import { EventBus } from '#/standalone/event-bus'
export default {
name: 'UserMedia',
data() {
return {
feed: null,
imgUrl: null
}
},
mounted() {
EventBus.$on('profile-media', (media) => {
this.$set(this, 'feed', media)
})
},
methods: {
zoomImage(url) {
this.$set(this, 'imgUrl', url)
},
visibilityChanged(isVisible, el) {
if(isVisible){
el.target.classList.remove('hide')
el.target.classList.add('show')
}
}
}
}
</script>
<style scoped>
.col-img {
height: 420px;
}
</style>
Since if you change the data 'feed', it will take time to load the images but inorder to load small size images prior to heavy size for good user experience you can use some very good npm packages:
npm i vue-lazyload (I have used it and recommend this one)
npm i v-lazy-image (I haven't used yet but you can explore this as well)

Laravel 6.4.1 + webpack + vue + scss dynamically loaded

I have laravel 6.4.1 with minor changes to the default webpack config.
I'm using vue components with scoped styling as well.
When I run npm run dev, everything works as it should. My Vue component is loaded and has styling.
When I run npm run production, my Vue component is not loaded.
Or well... The JS file is loaded, but the component never fires created or mounted and is not visible on screen and not visible in the DOM.
How do I know it's loaded then?
When I put console.log('test') above (or below) the export default it is displayed in the console.
When I remove the <style scoped lang="scss"> tag completely, my component is visible on screen as well.
I've already tried deleting parts of the styling, but it never works. Even an empty style tag will not render the component. It will only work when I fully remove it.
Ofcourse, I want to keep my styling in the component, so how can I fix this problem?
I've removed some JS from the Vue component to make it more readable and since I strongly suspect the issue in not in the JS I don't think it has any value for this issue.
webpack.mix.js
const mix = require('laravel-mix');
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel application. By default, we are compiling the Sass
| file for the application as well as bundling up all the JS files.
|
*/
mix
.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css');
if (mix.inProduction()) {
mix.version();
} else {
mix.sourceMaps();
}
// webpack.mix.js
const path = require('path'),
WebpackShellPlugin = require('webpack-shell-plugin'),
BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin,
{CleanWebpackPlugin} = require('clean-webpack-plugin');
mix.webpackConfig({
plugins: [
new WebpackShellPlugin({
onBuildStart: [
'php artisan js-localization:export --quiet',
'php artisan ziggy:generate resources/js/ziggy-routes.js --quiet'
]
}),
new BundleAnalyzerPlugin({
analyzerMode: mix.inProduction() ? 'disabled' : 'server',
openAnalyzer: false
}),
new CleanWebpackPlugin({
dry: false,
cleanStaleWebpackAssets: true,
cleanOnceBeforeBuildPatterns: [],
cleanAfterEveryBuildPatterns: [
'js/chunk/*'
],
dangerouslyAllowCleanPatternsOutsideProject: true,
}),
],
resolve: {
alias: {
ziggy: path.resolve('vendor/tightenco/ziggy/dist/js/route.js'),
},
},
output: {
publicPath: '/',
chunkFilename: 'js/chunk/[name].[chunkhash].js',
},
});
Vue component
<template>
<div class="position-relative my-2" :style="{backgroundColor: properties.settings.backgroundColor}">
<block-template-content-text
v-if="canDisplayBlock(block, 'content', 'text')"
:text-config="tinyMce.text"
:block="block"
#block-change="storeBlockChange">
</block-template-content-text>
<block-template-content-text-image
v-if="canDisplayBlock(block, 'content', 'text-image')"
:text-config="tinyMce.text"
:image-config="tinyMce.image"
:block="block"
#block-change="storeBlockChange">
</block-template-content-text-image>
<div class="d-flex align-items-center justify-content-center flex-column position-absolute controls">
<i class="fas fa-2x fa-fw fa-chevron-up cursor-pointer" #click="$emit('sort-item', 'up', block)"></i>
<div>
<i class="fas fa-1x fa-fw fa-plus-circle cursor-pointer" #click="$emit('add-new-block', block)"></i>
<!-- delete section start -->
<i class="fas fa-1x fa-fw fa-trash cursor-pointer"
:id="'delete-' + block.hash"
#click="$emit('delete-block', $event, block)"></i>
<b-tooltip :target="'delete-' + block.hash"
:id="'tooltip-' + block.hash"
triggers="focus">
<button type="button"
class="btn btn-primary"
#click="$emit('cancel-delete-block', block)">
{{ Lang.get('general.buttons.cancel') }}
</button>
<button type="button"
class="btn btn-danger"
#click="$emit('delete-block', $event, block)">
<i class="fas fa-fw fa-trash"></i>
{{ Lang.get('general.buttons.delete')}}
</button>
</b-tooltip>
<!-- delete section end -->
<!-- popover start -->
<div class="popover-content" :hidden="!showPopover">
<i class="fas fa-fw fa-times close-popover cursor-pointer" #click="closePopover"></i>
<div class="form-group">
<label for="background-color">
{{ Lang.get('project.design.pages.block.settings.background-color') }}
</label>
<input type="color"
name="background-color"
id="background-color"
class="form-control"
v-model="properties.settings.backgroundColor"
value="#ffffff"
#input="setSetting('backgroundColor', $event)">
</div>
</div>
<i class="fas fa-1x fa-fw fa-cog cursor-pointer"
#click="togglePopover"></i>
<!-- popover end -->
</div>
<i class="fas fa-2x fa-fw fa-chevron-down sort-order cursor-pointer"
#click="$emit('sort-item', 'down', block)"></i>
</div>
</div>
</template>
<script>
// Bootstrap Vue
import {TooltipPlugin} from 'bootstrap-vue';
Vue.use(TooltipPlugin);
// TinyMCE editor
import 'tinymce/tinymce.min';
import 'tinymce/themes/silver/theme.min';
import 'tinymce/plugins/paste';
import 'tinymce/plugins/link';
import 'tinymce/plugins/imagetools';
import {EventBus} from "../../vue/EventBus";
export default {
name: "display-block",
components: {
'block-template-content-text': () => import('./blocks/content/Text'),
'block-template-content-text-image': () => import('./blocks/content/TextImage'),
},
props: {
block: {
required: true,
type: Object
}
},
data() {
[...]
},
methods: {
[...]
},
mounted() {
[...]
}
}
</script>
<style scoped lang="scss">
#import "../../../sass/variables";
#import "~bootstrap/scss/mixins";
#import "~bootstrap/scss/bootstrap-grid";
#import "~bootstrap/scss/utilities/position";
#import "~bootstrap/scss/popover";
// Bootstrap Vue
#import '~bootstrap-vue/src/index.scss';
.block {
.block- {
&text {
#import '~tinymce/skins/ui/oxide/skin.min.css';
#import '~tinymce/skins/ui/oxide/content.min.css';
#import '~tinymce/skins/content/default/content.min.css';
}
}
}
.controls {
#extend .position-relative;
top: map_get($sizes, 50);
right: - map_get($spacers, 5);
transform: translateY(-50%);
&:first-child {
#extend .d-none;
}
.popover-content {
#extend .popover;
#extend .p-2;
min-width: 150px;
min-height: 150px;
.close-popover {
#extend .position-absolute;
#extend .mt-2;
#extend .mr-2;
top: 0;
right: 0;
}
input {
&[type=color] {
#extend .p-0;
width: 25px;
height: 25px;
border: none;
}
}
}
}
</style>
page.js
import {EventBus} from "../../vue/EventBus";
import {TooltipPlugin} from 'bootstrap-vue';
// import DisplayBlock from "../../components/project/DisplayBlock";
if (document.getElementById('page-editor')) {
// Bootstrap Vue
Vue.use(TooltipPlugin);
new Vue({
el: '#page-editor',
components: {
BlockModal: () => import('../../components/project/BlockModal'),
DisplayBlock: () => import('../../components/project/DisplayBlock'),
// DisplayBlock
},
data: {
[...]
},
computed: {
[...]
},
watch: {
[...]
},
methods: {
[...]
},
mounted() {
[...]
}
});
}
In your webpack file the resolver is missing for VUE.
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js'
}
}
Try by adding the above snippet.