I got started with Svelte(Kit) and I really like it so far. However, I ran into an issue that I couldn't resolve.
I'm trying to dynamically display images and like to have a fall back image in case the normal one does not exist.
With vanilla HTML/JS, I would use this Source:
<img src="imagefound.gif" onerror="this.onerror=null;this.src='imagenotfound.gif';" />
I tried to make it happen in my Svelte project, but it either does not work (e.g. A) or it works sometimes (for a brief moment or until I refresh (e.g. B)).
This is my code (A):
<script>
let profileImg = person.profile;
const missingProfile = () => {
console.log('image does not exist');
profileImg = '../default.png';
};
</script>
...
<img
src={profileImg}
on:error={missingProfile}
alt={person.name}
/>
or just the hardcoded version (B)
<img
src="imagefound.gif"
onerror="console.log('image not found');this.onerror=null;this.src='./person.png';"
/>
There is this question, but I believe the answer is what I am doing. Is there a difference whether I use SvelteKit vs. Svelte?
Addition
It looks like that Svelte does not even set the src for the <img />, if the original variable with the url for the image is undefined (in my example person.profile). The accepted answer wouldn't work in that case.
I edited this line to prevent that case:
let profileImg = person.profile === undefined ? '../default.png' : person.profile;
This code works in SvelteKit
<script>
let fallback = 'http://placekitten.com/200/200'
let image = ""
const handleError = ev => ev.target.src = fallback
</script>
<img src={image} alt="" on:error={handleError}>
Related
Minimum Reproducible Example on Github
I'm trying to inject some images into my pages created from markdown. I'm trying to do this using ReactDomServer.renderToString()
const componentCreatedFromMarkdown = ({data}) => {
...
useEffect(() => {
const injectDivs = Array.from(document.getElementsByClassName('injectDivs'))
injectDivs.forEach((aDiv) => {
aDiv.innerHTML = ReactDOMServer.renderToString(<Img fluid={data.allFile.edges[0].node.childImageSharp.fluid} />)
}
})
...
}
The img is showing as a black box, if I right click the image I can open it in a new tab, which shows the image as it is supposed to be.
How I understand the problem
The image html is correctly inserted into the page
gatsby-image loads the lowest quality image and applies some inline
styles. All that information is present in the html) This is done to
enable the blur-up effect on the image for a better UX.
The client side code that would load a higher resolution image and remove the inline styles is never applied.
Useful diagnostic information
The function inside useEffect does not get run on the server-side, but rather on the client after the component has mounted. It's possible to verify this by adding a console.log statement inside the effect and seeing that it is logged on the browser's console.
Source of the problem
ReactDOMServer.renderToString() only builds the HTML that's required by a component, it then requires the component to be hydrated.
Direct fix
You can use ReactDOM.render to put the image on the page, given that the effect will execute client side. I have a PR showing that here.
Recommended approach
You could instead import the image inside the mdx component and directly render it there. Your gatsby config can already support this 👍 In content/blog/hello-world/index.mdx, you can replace the injectImage div with this ![A salty egg, yummm](./salty_egg.jpg) (where the text inside the [] is the alt text)
This documentation might be helpful
https://www.gatsbyjs.org/docs/working-with-images-in-markdown/
https://www.gatsbyjs.org/packages/gatsby-remark-images/
Hope that helps! 😄
I have faced this problem before with my first Gatsby project.
The problem similar to this chat and the error image issue here.
If you replace GatsbyImageSharpFixed_withWebp_tracedSVG like they talked in spectrum chat above, or this is my current code for example, basically is related to WebP new image format.
export const query = graphql`
query {
allImageSharp {
edges {
node {
fluid(maxWidth: 110) {
aspectRatio
originalName
sizes
src
srcSet
srcWebp
tracedSVG
}
}
}
}
}
`;
Hope this help.
I think the problem could be in the useEffect
const componentCreatedFromMarkdown = ({data}) => {
...
useEffect(() => {
const injectDivs = Array.from(document.getElementsByClassName('injectDivs'))
injectDivs.forEach((aDiv) => {
aDiv.innerHTML = ReactDOMServer.renderToString(<MyComponent args={myArgs} />)
}
},[])
...
}
I think you should try to add useEffect(()=>{},[]), the second argument to refresh the image only when the component is mounted, without this the image will be refreshed each time.
This code is supposed to toggle between the "like" and "unlike" images, except it doesn't. Can anybody tell me what i wrote wrong. And yes i am new to javascript.
<script>
function imgclick(){
var like = "like.png",
unlike = "unlike.png";
var liked = document.getElemendById("liked");
liked.src = (liked.src === unlike)? like : unlike;
}
</script>
<img src="unlike.png" id="liked" width="200" height="200" onclick="imgclick();">
You made a typo in the following line:
var liked = document.getElemendById("liked");
You put getElemendById when it should be getElementById:
var liked = document.getElementById("liked");
If you are new to JS I just want to recommend to you to use relative paths every time you can, because it is going to save you a headache in the future.
It goes like this: var like = "./like.png"
I have a very strange situation . Here i will put the part of my code which will describe you the full problem.
const handleImageError = e => {
e.target.onerror = null;
e.target.src = 'factory1.svg';
};
<img src={`${data}?size=20`} onError={handleImageError} />
I am getting this kind of infinite loop `
The strange behaviour is when I change my icon to factory2.svg it works very well and loads the SVG file (200 OK).
What could be the problem?
would need to see more code but maybe try giving your ternary statement a way to break if it fails, looks like you only have a true statement..
img src={`${data} ? size=20: what to do if false here`}
could be possible that the image you put in that works is passing the condition and this one is not.
I am trying to make a basic Instagram web scraper, both art inspiration pictures and just generally trying to boost my knowledge and experience programming.
Currently the issue that I am having is that Casper/Phantomjs can't detect higher res images from the srcset, and I can't figure out a way around this. Instagram has their srcsets provide 640x640, 750x750, and 1080x1080 images. I would obviously like to retrieve the 1080, but it seems to be undetectable by any method I've tried so far. Setting the viewport larger does nothing, and I can't retrieve the entire source set through just getting the HTML and splitting it where I need it. And as far as I can tell, there is no other way to retrieve said image than to get it from this srcset.
Edit
As I was asked for more details, here I go. This is the code I used to get the attributes from the page:
function getImages() {
var scripts = document.querySelectorAll('._2di5p');
return Array.prototype.map.call(scripts, function (e) {
return e.getAttribute('src');
});
}
Then I do the standard:
casper.waitForSelector('div._4rbun', function() {
this.echo('...found selector ...try getting image srcs now...');
imagesArray = this.evaluate(getImages);
imagesArray.forEach(function (item) {
console.log(item);
However, all that is returned is the lowest resolution of the srcset. Using this url, for example, (https://www.instagram.com/p/BhWS4csAIPS/?taken-by=kasabianofficial) all that is returned is https://instagram.flcy1-1.fna.fbcdn.net/vp/b282bb23f82318697f0b9b85279ab32e/5B5CE6F2/t51.2885-15/s640x640/sh0.08/e35/29740443_908390472665088_4690461645690896384_n.jpg, which is the lowest resolution (640x640) image in the srcset. Ideally, I'd like to retrieve the https://instagram.flcy1-1.fna.fbcdn.net/vp/8d20f803e1cb06e394ac91383fd9a462/5B5C9093/t51.2885-15/e35/29740443_908390472665088_4690461645690896384_n.jpg which is the 1080x1080 image in the srcset. But I can't. There's no way to get that item as far as I can tell. It's completely hidden.
I found a way around it in Instagram's case. Instagram puts the source picture in a meta tag within the head. So, using the code I'll paste below, you can call all of the meta tags and then sort out which one is the source picture by checking if "og:image" is retrieved.
function getImages() {
var scripts = document.querySelectorAll('meta[content]');
return Array.prototype.map.call(scripts, function (e) {
return e.getAttribute('property') + " " + e.getAttribute('content');
});
}
And this is the way to sort the meta tags into only having the original image in its native resolution.
this.echo('...found selector ...try getting image srcs now...');
imagesArray = this.evaluate(getImages);
imagesArray.forEach(function (item) {
if (typeof item == "string" && item.indexOf('og:image') > -1) {
Edit: Unfortunately this only works for single image posts on Instagram (the site I'm trying to scrape) so this unfortunately does me no goo. The values within the meta tags don't change even if you load the next image in the post. I'm leaving this up though in case anyone else could use it, but it's not ideal for my own use case.
Yes indeed PhantomJS doesn't seem to support srcset, its Webkit engine is very old.
But to be fair, all the metadata related to the page is out in the open in the HTML as JSON in window._sharedData variable.
If you want to use a headless browser (and not parse it with any server-side language) you can do this:
var imgUrl = page.evaluate(function(){
return window._sharedData.entry_data.PostPage[0].graphql.shortcode_media.display_resources[2].src;
});
https://instagram.fhen2-1.fna.fbcdn.net/vp/8d20f803e1cb06e394ac91383fd9a462/5B5C9093/t51.2885-15/e35/29740443_908390472665088_4690461645690896384_n.jpg
Solution: So my solution was to use slimerjs. If I run the js file through "casperjs --engine=slimerjs fileName.js", I can retrieve srcsets in full. So if I say use this code:
function getImgSrc() {
var scripts = document.querySelectorAll("._2di5p");
return Array.prototype.map.call(scripts, function (e) {
return e.getAttribute("srcset");
});
}
on this url (https://www.instagram.com/p/BhWS4csAIPS/?taken-by=kasabianofficial) I will get (https://instagram.flcy1-1.fna.fbcdn.net/vp/b282bb23f82318697f0b9b85279ab32e/5B5CE6F2/t51.2885-15/s640x640/sh0.08/e35/29740443_908390472665088_4690461645690896384_n.jpg 640w,https://instagram.flcy1-1.fna.fbcdn.net/vp/b4eebf94247af02c63d20320f6535ab4/5B6258DF/t51.2885-15/s750x750/sh0.08/e35/29740443_908390472665088_4690461645690896384_n.jpg 750w,https://instagram.flcy1-1.fna.fbcdn.net/vp/8d20f803e1cb06e394ac91383fd9a462/5B5C9093/t51.2885-15/e35/29740443_908390472665088_4690461645690896384_n.jpg 1080w) as the result.
This is what I wanted as it means I can scrape those 1080 images. Sorry for this messy page, but I wanted to leave my trail of steps to any of those who might be trying like me.
I am trying to get the gravatar of multiple uses to be displayed in one page and i am using a foreach loop for that. Also i am using knockout js to get the information like email and name and returned as json. Since i cannot use razor with gravatar because it requests a string to be passed in as an email and what i have are returned "data-bind="text:Email"
I am trying to use the MD5 concept and i am very new to this and not sure if i am following the right steps. I found this online from google code: http://www.devcurry.com/2012/06/retrieving-gravatar-using-jquery.html
and i tried to implement but not sure if my code is properly written:
Part of my javascript related to what i am doing:
$.views.Games.UserViewModel = function (data) {
var self = this;
self.Name = ko.observable(data.Name);
self.Email = ko.observable(data.Email);
self.Hash = CryptoJS.MD5(Email);
};
My View Page:
<img alt="Gravatar" title="My Gravatar" data-bind="attr:{href:'http://www.gravatar.com/avatar/' +Hash()+'?s=30&d=identicon&r=G'}" />
With this i am not getting a gravatar to show. Any helpful information or tips is greatly appreciated.
Code update
I altered so this is how it looks:
view model
public string MD5Email { get{ return Email.MD5Hash(); } }
javascript
self.MD5Email = ko.observable('http://www.gravatar.com/avatar/' + data.MD5Email + '?s=30&d=identicon&r=G');
view page
<img width="158" height="158" alt="Gravatar" data-bind="attr:{'src':MD5Email()}"/>
First, don't put JS logic in your data-binds. Use a computed instead:
self.GravatarUrl = computed(function() {
return 'http://www.gravatar.com/avatar/' + self.Hash() + 's=30&d=identicon&r=G';
});
Second, your Hash needs to be a computed observable as well. The way you have it now, it's only evaluated once, when the JS first runs, and when most likely Email is null. So your Gravatar URL is never being populated with a valid email hash.
self.Hash = computed(function() {
return CryptoJS.MD5(self.Email);
});
However, since this Hash computed is only being used to serve the other computed at this point, you can and should probably just combine the two:
self.GravatarUrl = computed(function() {
var hash = CryptoJS.MD5(self.Email);
return 'http://www.gravatar.com/avatar/' + hash + 's=30&d=identicon&r=G';
});
Then, your data-bind becomes simply:
<img alt="Gravatar" title="My Gravatar" data-bind="attr:{ href: GravatarUrl }" />
Much cleaner.
UPDATE based on OP's update
I'm not sure why you changed the logic of the code I gave you, but that's why it's not working.
First, it seems you've given up on MD5 in Javascript and added it to your view model. That's fine, but you've introduced the same logic problem you had earlier by setting the self.MD5Email to the entire URL based on data.Email. This is not a computed and data.Email is not an observable. It exists only at the initial creation of the KO view model. What you should be doing is something like:
self.MD5Email = ko.observable(data.MD5Email);
self.Gravatar = ko.computed(function () {
return 'http://www.gravatar.com/avatar/' + self.MD5Email() + '?s=30&d=identicon&r=G'
});
You've rightly corrected setting the img src instead of href (I missed that too), but using the parenthesis is unnecessary when you're not doing other JS logic at the same time, so it should just be:
data-bind="attr: { src: Gravatar }"
Not sure if this helps but I wrote a binding to do just this recently:
https://github.com/grofit/knockout.gravatar
So you can just use it using the knockout binding goodness.