Lazyload image in Vue/Nuxt gallery component - image

I'm trying to create a simple gallery component where if you click on some image a Light-Box will appear where you can see full size photo and have options like next and previous photo or close the Light-Box.
Currently When I need to change the image to next or previous I change the src of the img-tag and it works.
Here comes my problem. I want to lazy load my images. I use lazysizes in my project.
So the simple implementation to have an image to load is to add the class "lazyload" and to pass the property data-src instead of src.
However if I change to data-src my methods for next and previous image are not working.
< script >
export default {
props: {
data: {
type: Array,
required: true,
},
},
data: () => ({
visible: false,
currentImage: 0,
}),
methods: {
Toggle(index) {
this.currentImage = index
this.visible = !this.visible
},
Next() {
if (this.currentImage !== this.data.length - 1) this.currentImage++
},
Prev() {
if (this.currentImage !== 0) this.currentImage--
},
},
} <
/script>
<template>
<div id="gallery" class="gallery">
<!-- images grid -->
<div v-for="(item, i) in data" :key="'gallery-image' + i" class="image">
<img :src="item.image.thumbnail.url" #click.native="Toggle(i)" class="lazyload"/>
</div>
<!-- image lighbox on click -->
<div v-if="visible" class="lightbox">
<Icon class="cancel" #click="Toggle()"/>
<Icon name="left" :class="{ disable: currentImage == 0 }" #click="Prev()"/>
<img :src="data[currentImage].image.url" class="lazyload"/>
<Icon name="right" :class="{ disable: currentImage == data.length - 1 }" #click="Next()"/>
</div>
</div>
</template>
UPDATE
I forgot to add crucial code. To implement lazysizes in a Nuxt project we need to add in nuxt.config.js the fallowing code. You can read more here.
build: {
extend(config, { isClient, loaders: { vue } }) {
vue.transformAssetUrls.img = ['data-src', 'src']
},
},
As I investigate in the developer tools I found that when triggering click for method like Next image, the src of the image does not change, only the data-src. I'm guessing I need a way to trigger this transform so that everything can work as expected.

Also, on top of my comment, I do recommend looking into the official nuxt image module which do have native lazy loading out of the box: https://image.nuxtjs.org/components/nuxt-img
You could maybe combo this with some simple lightbox that does the trick for you. I've used vue-silentbox before, it is working pretty well.
You can have that kind of code there
<silent-box :gallery="photosToDisplay">
<template #silentbox-item="{ silentboxItem }">
<img :src="silentboxItem.src" :key="silentboxItem.id" />
</template>
</silent-box>
So, I guess that you could totally swap img by a nuxt-img there, and have it lazy-loaded.
The images are not lazy-loaded in the project, but here is a small project that I did to try out the lightbox if you want to quickly look how it renders (URL in the top right corner).

Probably this is not the most elegant way to do it . I force re-render to my image component. You need to assign a key value to component and whenever the value changes a new instance creates of the component.

Related

Reactive Vue Data Does Not Render in Firefox Columns

I've run into an extremely strange problem while developing a Vue SPA in Firefox (v89.0.1). Reactive data will not render on the page when it is contained within columns. Here is an example:
<template>
<div style="column-count: 2">
<div style="break-inside: avoid;">
Column 1
</div>
<div style="break-inside: avoid;">
Column 2
<div v-if="test">{{ test }}</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
test: ''
};
},
mounted() {
setTimeout(() => {
this.test = 'tested';
}, 2000);
}
};
</script>
In Chrome, the word "tested" is correctly rendered in the second column. In Firefox the word "tested" does not immediately appear, but it will appear later if the window is resized. The issue is resolved if the column-count style is removed.
Is this a known issue and, if so, does anyone have a suggested work around? Really scratching my head on this. (Note: I'm using column-count to create a masonry layout, so I can't easily substitute another grid solution.)

localStorage persisting with looped Vue component

I'm having the strangest time getting localStorage to work on my local machine. I seem to be losing indexes in the array I built, I've been trying for hours to figure out why with absolutely no luck. I've even tried different ways of building the array.
Here is the component I am loading with a v-for loop. this is working as expected.
home.vue
<tweets
v-for="tweet in tweets"
v-bind:key="tweet.id"
v-bind:tweet="tweet"
></tweets>
And here is the trouble-maker component. It loads a number of tweets that can be voted on. (Feel free to ignore the HTML, I'm not sure it's relevant.)
tweets.vue
<template>
<div class="col-2 d-flex">
<div class="align-self-center ml-3">
<div class="row py-1">
<i
class="fas fa-arrow-up"
style="font-size: 30px"
v-on:click="voteUp"
></i>
</div>
<div class="row py-1">
<i class="fas fa-arrow-down" style="font-size: 30px"></i>
</div>
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
localStorage: [],
};
},
props: {
tweet: Object,
},
created: function () {
this.localStorage = JSON.parse(localStorage.storageData);
console.log(this.localStorage);
},
methods: {
voteUp: function () {
axios
.get("/api/vote/2/1")
.then(({ data }) => {
var test = {
"tweet_id": 1,
"vote_status": 1
};
this.localStorage.push(test);
console.log(this.localStorage);
localStorage.setItem("storageData", JSON.stringify(this.localStorage));
console.log("go");
//persist?
})
.catch(function (error) {
alert("Take a screen shot and send this to me." + error);
console.log(error);
});
},
},
};
</script>
So if you take a look at my localStorage variable, I have it being set to the current localStorage found in the browser, however...it's having this strange problem where if I click on the button that triggers the voteUp function, it will sometimes delete some of the indexes in the array. I'm having trouble explaining so I've make a quick video to demonstrate.
https://streamable.com/kkhnrx
as you can see, I'm firing the event and the array starts growing, but as I scroll down to different buttons (remember the tweets are looped) the array will lose a few of it's indexes. I have no idea why this is happening. I assume it has something to do with the component being looped and scope problems?
This looks exactly like race condition. There are multiple component instances that compete over the storage, and it becomes overwritten eventually.
This makes an early copy that won't contain updates from other instances:
this.localStorage = JSON.parse(localStorage.storageData)
this.localStorage should be assigned immediately before updating the storage, or there should be a common state for all component instances (e.g. by using Vuex with persistence).
You have an error in your created function, right?
If you want to get "storageData" from local storage it should be:
created: function () {
this.localStorage = JSON.parse(localStorage.getItem('storageData'));
console.log(this.localStorage);
},

How to create React component that can render Img or Video depending on API data it recieves

I am currently learning and practicing React.
I am trying to achieve a react web app that allows you to display and scroll through Nasa's picture of the day based on what date you choose. here's the api: api.nasa.gov/
the project is simple BUT. sometimes the url that the api returns, leads to an img and sometimes a youtube video. for example:
https://apod.nasa.gov/apod/image/1708/PerseidsTurkey_Tezel_960.jpg
or
https://www.youtube.com/embed/Zy9jIikX62o?rel=0
I have managed to display both cases using an iframe. I know this is not ideal for the imgs because I cannot format it properly with css and it just looks bad with the scroll bars, sizing etc..but won't display the video..
this is what the react component looks like (using bootstrap)
detail.js
import React, { Component } from 'react';
export default class Detail extends Component {
render() {
return (
<div className="video-detail col-md-12">
<div className="embed-responsive embed-responsive-16by9">
<iframe className="embed-responsive-item" src={this.props.url}>
</iframe>
</div>
</div>
);
}
}
I would like to images also to display responsively, centered and original aspect ratio but also still have the videos display properly as they are.
I have tried putting an additional <img> underneath the <iframe> but that just renders the image twice. Is there a way to render an <img> or <iframe> conditionally?
I am wondering what strategy should I try next? Any ideas?
Thanks!
you should use conditional rendering
const isImage = this.props.url.split().pop() === 'jpg';
return (
{
isImage ? <img src={this.props.url} />
: <iframe className="embed-responsive-item" src={this.props.url}>
</iframe>
}
)
or
const isImage = this.props.url.split().pop() === 'jpg';
return (
{
isImage && <img src={this.props.url} />
}
{
!isImage && <iframe className="embed-responsive-item" src={this.props.url}>
</iframe>
}
)
Yon don't even have to use a split().pop(). Just scan the API return for a media type (much simpler, really):
const Photo = props => (
<div>
<h3 className="m-5 text-center">{props.photo.title}</h3>
{props.photo.media_type === "image" ? (
<img className="rounded mx-auto d-block" src={props.photo.url} alt={props.photo.title} />
) : (
<iframe
title="space-video"
src={props.photo.url}
frameBorder="0"
gesture="media"
allow="encrypted-media"
allowFullScreen
className="mx-auto d-block"
/>
)}
<p className="col-md-8 offset-md-2 mt-5 pb-5">{props.photo.explanation}</p>
</div>

How do I Change window size on kendo grid editor template?

I have a editor template for my kendo grid defined as
<script id="my-editor-template" type="text/x-kendo-template">
<div class="k-edit-label">
<label for="ContactName">Contact</label>
</div>
<div data-container-for="ContactName" class="k-edit-field">
<input type="text" class="k-input k-textbox" name="ContactName" data-bind="value:ContactName">
</div>
<!-- more fields, etc -->
</script>
In my grid definition, I definte editable like this:
editable =
{
mode: 'popup',
template: kendo.template($('#my-editor-template').html()),
confirmation: 'Are you sure you want to delete rec'
};
But I would like to make the popup window wider. I tried wrapping the contents of my template in a
<div style="width: 800px;"></div>
but the popup window stayed the same with, and made the contents scrollable (i.e., 800px content inside a 400px window).
I know I can do a
$(".k-edit-form-container").parent().width(800).data("kendoWindow").center();
after the window is opened, but all the content of the window is formatted for like 400px, and it feels a little hackish. Is there not a way I can dictate teh size in the template markup?
Kendo grid popup window is using kendo window, so you can set the height and width like this
editable: {
mode: "popup",
window: {
title: "My Custom Title",
animation: false,
width: "600px",
height: "300px",
}
}
Here is dojo for you, but since i did not define a custom template it still use default one so as the result the window size already 600 x 300 but the content is not.
After an hour+ long research following code fixed my issue. I had to put this code in the edit event.
$(".k-edit-form-container").attr('style', "width:auto");

Custom thumbnail on SlideJS plugin

Is there a way to make the thumbnail images different from the fullsize images on a gallery using SlideJS plugin?
OUR SOLUTION:
We also didn't found anything on the documentation and solved it the following way:
Create a custom attribute on the HTML of the thumbnails gallery:
<div id="thumbnails">
<img src="/path/to/thumbnail.jpg" fullsize="/path/to/fullsize.jpg" />
<img src="/path/to/thumbnail.jpg" fullsize="/path/to/fullsize.jpg" />
<img src="/path/to/thumbnail.jpg" fullsize="/path/to/fullsize.jpg" />
[...]
</div>
<div id="slideshow"></div>
When some thumbnail gets clicked, make a copy of the thumbnails div HTML, switch the attribute values and set the slideshow:
$('#thumbnails img').click(function() {
// Get a copy of element instance of thumbnails div
var thumbnails = $(this).parent();
// On that copy, switch "img" attribute with "fullsize" attribute of each image
// and remove "fullsize" attribute for cleanliness
thumbnails.children('img').each(function() {
$(this).children().first().attr('src', $(this).children().first().attr('fullsize'));
$(this).children().first().removeAttr('fullsize');
});
// Set the switched HTML to the div that will hold the slideshow
$('#slideshow').html(thumbnails.html());
// Set the slideshow
$('#slideshow').slides({
start: 1,
preload: true,
generateNextPrev: true,
generatePagination: false,
effect: 'fade'
});
});

Resources