preload images on javascript (React, Jquery) app, images loaded twice - javascript

Please look at EDIT 3 below, as I figured out that it is not an issue with React but with the browser's chaching mechanism.
I'm trying to create an app that creates some simple carousel out of given array of images url. I've written a module that helps me to invoke a callback once all the images are loaded, this is how it look:
ImagesLoader.js
export default {
// a module that loads an array of images urls and when completes
// calls a callback with the images elements array
load(imagesUrlsArray, callback){
const imagesLoaded = [];
imagesUrlsArray.map((url) => {
const img = new Image();
img.onload = () => {
imagesLoaded.push(img);
if(imagesUrlsArray.length === imagesLoaded.length){
callback(imagesLoaded);
}
};
img.src = url;
});
}
}
I know I can use promises and resolve once all urls are loaded, and no check for errors right now.
Here is where my component updates the state with the images retriveed from the module above:
componentDidMount() {
ImagesLoader.load(this.props.images, (loadedImages) => {
const imagesStateData = this.calculateImagesStateData(loadedImages);
this.setState(Object.assign({},
imagesStateData,
{
loaded: true,
loadedImages: loadedImages
}
));
});
}
What happens is when I click the next or previous button (look at the screenshot below) each time the image loaded again, I can't understand why.
this is the first time I do such thing with reactjs, with jquery had no problems
Here is how I pass the images urls:
export default class App extends Component {
render() {
var images = [
"http://www.wallpapereast.com/static/images/excellent-love-quotes-wallpaper-hd.jpg",
"http://www.wallpapereast.com/static/images/My-Samsung-Galaxy-S3-Wallpaper-HD-Landscapes1.jpg",
"https://s-media-cache-ak0.pinimg.com/236x/58/01/02/5801020fea36221ffba33633f99a7d81.jpg",
"http://www.wallpapereast.com/static/images/pier_1080.jpg",
"http://www.wallpapereast.com/static/images/wallpaper-black-hd-hd-wallpapers.jpg",
"http://1.bp.blogspot.com/-dvg12YJKaKg/UnVfkMke7jI/AAAAAAAAUaU/O86x5FMgEuk/s1600/longcat.gif"
];
var settings = {
autoPlay: true,
arrows: true
};
return (
<Layout>
<ReactCarousel images={images} width={500} height={300} settings={settings}/>
</Layout>
);
}
}
EDIT:
managed to give a short example. http://codepen.io/anon/pen/ObyRPX , what I could see from building this example is if I don't use the ImageItem component and render a simple <img src='image.src'/> element it works good, so I guess my problem is with the ImageItem component.
EDIT 2:
this is pretty weird http://codepen.io/anon/pen/eBpdjx here I just changed it so ImageItem renderes image element rather than background image on a div and it works as expected. Can anyone explain what is the difference?
EDIT 3:
Apparently this is happening not only on react apps but on jquery app aswell, have a look here:
http://codepen.io/anon/pen/VmvyPZ
Again when I try to load the image as background-image property of the css, the browser doesn't cache the image and load it twice.

I faced the exact same issue. The image gets loaded again because the browser is not holding a reference to the image that you have loaded.
All I did was create a global array and push all the images to the array.
Now, this array is global and the browser has to maintain the array until the whole app gets destroyed.
var allImages = [];
function loadImages(imageUrls) {
imageUrls.forEach((q) => {
const img = new Image();
img.src = q;
allImages.push(img);
});}
After this, I was able to load the images on other pages, even when offline.

Chrome shows 2 image requests in devtool for one image. Try to uncheck disable cache option in the network tab.

Related

Image onload not being called in vue

I am trying to build a Vue App and I am new to Vue, I am trying to load an image at runtime in Js/Ts with the code below
console.log("code is being called");
let image = new Image();
image.onload = (event) => {
console.log("image loaded");
}
image.src = "static/prison_tileset.png";
The code works fine on a regular html file, in Vue however, the onload method isn't being called. The first console.log is being called. I am sure that the image is found because if I manipulate the path to something wrong on purpose I get an error that the file isn't being found. It's just that the onload event isn't being firied.
I'd be very happy if somebody could help me with that!
I've gotten it to work now! I will answer the question myself as best as I can. To use local assets in view you have to load them via Webpack like this:
const Logo = require("../assets/logo.png");
this.image.src = Logo;
If you also happen to use electron you have to set the webSecurity in your entry file to false for electron to be allowed to access local files.

Rendering image dynamically is so hard in React

After trying various ways for hours and checking every relatable link, I couldn't find any proper way to render image dynamically in React.
Here is what i am trying to do.
I have an array of objects in which each object has a attribute called name. I am using map function map to loop over this array and returning of array of img element like shown below.
<img className="img-thumbnail" src={require('../../public/images/'+item.name+'.png')}/>
where item.name is the name of image file I want to display, for which require is giving me error "cannot find module".
Moreover I need to implement some fallback option, where rather showing broken images incase image file does not exist, i want to display default image
Here are the things I have tried:
using try and catch block over require and calling this function from img element
setImage(data){
try{
return require( '../../public/images/'+data+'.png' ) //actual image
}catch(err){
console.log(err);
return require('../../public/images/fallback.png'); //fallback
}
<img className="img-thumbnail" src={this.setImage(item)}/>
using import, inside same function above, got error import cannot be called from inside of function
using react-image library. Turned out it does not support local images.
Any help ?
Here a tricky way to handle this. Use react state to check if there's error.
If true, show fallback, otherwise, show actual image.
setImage = (data) => {
const image = new Image();
image.src = '../../public/images/'+data+'.png';
this.setState({
hasError: false
})
image.onerror = () => {
this.setState({
hasError: true
})
}
return image.src;
}
// into render
this.state.hasError
? <img src="../../public/images/fallback.png" />
: <img className="img-thumbnail" src={this.setImage(item)}/>
Update: Example
var image = new Image();
image.src = 'fake.jpg';
image.onerror = () => {
console.log('image doesn t exist');
}
I dont know why you need required it could be done simply like this. You can import something like this. Import image like this
import fallback from '../../public/images/fallback.png';
and for dynamic image i would suggest either make some key value pair. For ex :
let data = {
image1 : ../../public/images/image1.png,
image2 : ../../public/images/image1.png
}
and import it normal
and something in render
it could be something like this.
render(){
return(
<img className="img-thumbnail" src={img?img[type]:fallback}/> //something its just refrence(where type is something which you want dynamically add image)
)
}
Requires are statically checked during compile time. The path of requires cannot be dynamic. Since you have static images in your bundle and the object maps to one of these you can follow a solution to something as follows
const images = {
image1: require('local/path/to/image1'),
image2: require('local/path/to/image2'),
image3: require('local/path/to/image3'),
}
const defaultImage = require('local/path/to/defaultImage');
const Img = ({ name }) => {
// here name is the name for the image you get from api..
// This should match with the keys listed the iages object
return <img src={images[name] ? images[name] : defaultImage}/>
}
Above all answers were helpful but unforutnaley none of the method worked for me. So again digging little deep I found that require was giving error "cannot find module" because after webpack bundles my code, require lost the context. Which means the given relative path was no longer valid.
What i needed to do was preserve context which I did by using require.context;
Here is the final code that worked.
//getting the context of image folder
const imageFolderContext = require.context('realtive/path/to/image/folder')
//function to check if image exist
checkFile(data){
try{
let pathToImage = './path/to/image/relative/to/image/folder/context
imageFolderContext(pathToImage) //will check if Image exist
return true //return true if exist
}catch(err){return false}
}
//rendering image element dynamically based on item name and if exist or not
<img src={this.checkFile(itemName)?imageFolderContext('path/to/image/relative/to/context'):imageFolderContext('default/image/path/) />
don't forget to bind checkFile function

get image size before upload in React

The desired results have been done in vanilla here
To be clear, I won't be using alert(), I'd like to place the dimensions in the state object.
How would I recreate this code in React under the OnChange() event handler?
React is still JavaScript, everything you do in regular js can be done in React. At a high level, React is just the framework that helps you create more modular reusable code.
With that being said, thinking as a React developer, you should create the upload process first get that to work in a separate class. The class could accept an image through its props. Then once that is working correctly you could create a Higher order component that wraps the upload component or rather receives the upload component as a parameter and checks the image size. At this point you can return an Invalid component or just process the image and upload.
You might want to look at a few tutorial on Getting started with React.
This is an excellent start egghead.io
I have a img src that looks something like this:
I was able to solve the problem with code like this
if (e.target.files && e.target.files[0]) {
var img = document.createElement("img");
this.state.backgroundImageFile = e.target.files[0];
img.onload = function () {
debugger;
console.log(this.width + " " + this.height);
});
var reader = new FileReader();
reader.onloadend = function (ended) {
img.src = ended.target.result;
}
reader.readAsDataURL(e.target.files[0]);
}

Dynamically adding data to page with aurelia after the view is rendered

I am trying to build a web app with aurelia and I couldn't find a way to add data via AJAX after everything on page has been rendered. The scenario (simplified):
There's a page, some part of which is dynamically composed from a component (say, data-table). data-table has a title, and table-rows to show the data. data-table should load it's data dynamically via an AJAX call. What I want is, loading the data after the page is rendered.
I tried using promises but it does the opposite, ie, aurelia waits until the promise is resolved before attaching the view (explained by Jeremy Danyow as well: "...In this example Aurelia will wait for the Promise returned by the activate method to resolve before binding the view to the viewmodel." (in his post titled "ES7 async/await with Aurelia")
This causes the page remain stalled until all data is loaded.
A simple code example is provided below. Here, if you navigate to this page, you won't see anything (or the page wont get attached) until all data is loaded. What I want is, to load the page and show "Table for ..." title, and at the same time start loading data in the background and show the table itself when loading completes. The desired behavior is illustrated in the following "mock" screenshots.
before the ajax requrest is completed
after the ajax request is completed
Additionally, the tables may need to be updated based on user choices (additional data may be loaded and added to the tables) or additional tables may need to be added to the page.
I don't think the desired behavior matches any of the bind/attached/detached etc. life-cycle behaviors (but could be wrong). This could be implemented utilizing a variant of body.onload (or jquery etc.) but I wonder if this is possible to do using aurelia only (or mostly).
Maybe, being able to load data after everything is attached (eg. a "postattached" callback) could help. In that case, I would load all necessary components with their already loaded data (eg. their titles) and show them. Then, in the "postattached" section I will start loading data.
Sample code:
test.ts
export class testPage {
ids: number[] = [1,2,3] // for example 1,2,3; will be dynamically loaded as well
}
test.html
<template>
<h1>Test</h1>
<div repeat.for="id of ids">
<compose view-model="./components/table" model.bind="id"></compose>
</div>
</template>
table.ts
import { Loader } from './loader';
export class table {
id: number
tableData: number[][] = []
activate(model) {
this.id = model
}
attached() {
Loader.LoadData(this.id).then((res)=>{this.tableData = res})
}
}
table.html
<template>
<h2>Table for ${id}</h2>
<div repeat.for="rows of tableData">${rows}</div>
</template>
loader.ts
export class Loader {
static LoadData(tid): Promise<number[][]> { //simple stub to imitate real data loading
let data: number[][] = []
switch (tid) {
case 1:
data.push([11, 12, 13])
data.push([14, 15, 16])
break;
case 2:
data.push([21, 22, 23])
data.push([24, 25, 26])
break;
case 3:
data.push([31, 32, 33])
data.push([34, 35, 36])
break;
}
this.sleep()
return new Promise((resolve, reject) => {
this.sleep()
resolve(data)
})
}
protected static sleep(): boolean { // just to imitate loading time
let miliseconds = Math.floor(Math.random() * (3 - 1 + 1) + 1);
var currentTime = new Date().getTime();
console.debug("Wait for a sec: " + miliseconds)
while (currentTime + miliseconds * 1000 >= new Date().getTime()) {
}
return true
}
}
edit: Corrected code which was miscarried to the example here
You should try using window.timeout to emulate the loading time. Because Javascript is single threaded, your sleep function will block all other execution on the thread.
This answer is possibly a bit over the top but explains in more detail how to write a sleep function in javascript: What is the JavaScript version of sleep()?

How can you dynamically change the source in a require statement that requires an image file?

The image I'm using in React Native uses a require statement for the source of the image. I want to change the source of the image so it is kind of of like switching out frames in an animation.
var module1 = './hello';
var module2 = './goodbye';
state = {
module: module1 // will later be changed to module2
}
require(this.state.module);
Require both and then use the correct one where you need it:
var hello = require('./hello');
var goodbye = require('./goodbye');
hello.myFunction();
// later:
goodbye.anotherFunction()
I dont think it is a good programming style but it is possible.
For example the modules look like this
exports.name = () => {
console.log('modul1')
}
And the main file like this, it will work like expected
const modul1 = './modul1'
const modul2 = './modul2'
var modul = require(modul1)
modul.name() // -> modul1
modul = require(modul2)
modul.name() // -> modul2
I ended up just creating an object like this:
var frames = {
'1': require('./assets/phase1.png'),
'2': require('./assets/phase2.png'),
'3': require('./assets/phase3.png'),
'4': require('./assets/phase4.png'),
}
and in my Image tag, I set the source attribute to this.state.frame and setState with a new key from frames like frames[imageIndex]. Luckily I didn't have too many images, but this isn't the most ideal solution.
EDIT:
Another solution (which is more elegant and less verbose) is to create a drawable folder in android>app>src>main>res and drop all the images in the that folder. On iOS, you can drop all your images in ios>Appname>Images.xcassets.
From there, you can set the source of your images to {uri: this.state.imageFile} where this.state.imageFile is a string without the file extension and you can change them dynamically without having to require() each file.

Categories

Resources