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.
Related
I have an odd issue. I am attempting to set a CSS variable and change this dynamically on page load.
It works fine in my rudimentary jsfiddle here: https://jsfiddle.net/lharby/jw6t0krm/
The basic code is:
const colours = ['#ee4800','#60c600','#00ffff','#ff7f00','#ff6ccc','#848382','#d2dd26'];
const rndColour = Math.floor(Math.random() * colours.length);
document.documentElement.style.setProperty("--selection-background", colours[rndColour]);
My app is more complex in using transpiling, so my setColourFunction looks like this:
const colours = ['#ee4800','#60c600'...]; // same as previous array
const rndColour = Math.floor(Math.random() * colours.length);
const setRandomColour = () => {
document.documentElement.style.setProperty(
'--selection-background',
colours[rndColour]
);
};
export { setRandomColour };
Then, in my main app, I am importing like so:
import { setRandomColour } from './js/randomColours';
$(document).ready(() => {
if (DOM.classList.contains('high-contrast')) {
setRandomColour();
}
}
In the browser, the background colour is set as a pink tone by default (which is one of the items inside the colour array). If I hover over the scrollbar or click the navigation, the scrollbar changes colour to the correct value in the variable. I assumed this might be some Chrome bug, but the jsfiddle seems to work fine.
You can see an example of the live app here: https://studiomalarkey.co.uk/
At one time, I did have a reference to my minified script using the defer method, and thought this might be the issue, but I have removed that property now.
Does anyone know why this might be happening?
I want to make a product presentation page where I retrieved the addresses of the images, then I have but the address that I retrieved it but the image does not upload (the link is correct).I found some source which said that you have to use require ('../ image / name_image.jpg') when I have but the code like it works but when I used list.image (variable) it does not work
the result that i got it
the code that creates the image
get data results
file structure
Add your image folder inside the public folder: public/image/
then use <img src="/image/m1.jpg" />
You can try this and you need to declare state images with empty array, and declare the function loadImages that will load your images dynamically:
loadImages() {
lists.forEach((item, i) =>
import(item.image)
.then(image => {
this.setState(state => ({ images: [...state.images, image] }))
})
)
}
In your jsx edit this line:
src={this.state.images[index]}
or you can use:
<img src={require(../image/m1.jpg)}/>
I have a new version of my site which uses VueJS v2 (the previous one didn't). The main code is placed inside <div id="app"></div> and Vue is initiated. The issue is that I partner with an advertising company called Ezoic that injects ads through using AI onto the page, but these ads aren't displaying properly. I believe it is related to these errors:
https://pagead2.googlesyndication.com/pagead/show_ads.js
show_ads.js:53 Failed to execute 'write' on 'Document': It isn't
possible to write into a document from an asynchronously-loaded
external script unless it is explicitly opened.
Ezoic works with Google Ad Exchange, so I believe it is the above line that's related to the issue.
I'm wondering, is there any way in which I could make my application compatible with Ezoic/Adsense? I thought about having Vue on the page only where needed, rather than the entire page (<div id="app"></div> goes from the start of body to the end of body), but this would mean I need multiple Vue instances running as I have components at the top (search box) and also throughout the pages.
I have no access to the code that Ezoic inject onto the page as this is done on their end (my site uses their DNS and they modify the response before sending to the visitor, to include the ad code). Ezoic team is also having a look into this issue presently but any information I could pass along could be helpful!
At the request of Dynamic Remo I am submitting an answer for Ezoic's standalone implementation that is compatible with Vue.
I will however preface this with they absolutely hate it when you install it this way and essentially refuse to support it. - with that said you have way more control over placement
The Solution:
First add the following script tag somewhere outside or your custom defined root element.
<script type="text/javascript" src="//www.ezojs.com/ezoic/sa.min.js" async=""></script>
In your vue component you will need to create all your placeholder elements with a id of "ezoic-pub-ad-placeholder-xx" where xx is replaced by the actual id found in your enzoic dashboard. Dynamic creation does work as long as there is a matching id in ezoic so you have two options:
Dynamic:
<div v-for="placeholderId in ezoicArray" :id="'ezoic-pub-ad-placeholder-' + placeholderId" class="ezoic"></div>
Standard:
<div id="ezoic-pub-ad-placeholder-102" class="ezoic"></div>
To actually display ads in your placeholders you can use this function I wrote. Just call it when the component is mounted.
ezoic(placeholderList) {
if (window.ezstandalone !== undefined) {
window.ezoicPlaceholderArray = window.ezoicPlaceholderArray || [];
// Add Placeholders to Array
placeholderList.forEach((placeholder) => {
this.addPlaceholderOnce(window.ezoicPlaceholderArray, placeholder);
});
// Enable Once - Refresh on Change
window.ezoicRefreshed = false;
window.ezoicEnabled = window.ezoicEnabled || false;
// Next Tick Ensures All Enzoic Blocks Are Loaded
this.$nextTick(() => {
// On First Load We Must Enable
if (!window.ezoicEnabled) {
window.ezstandalone.define(window.ezoicPlaceholderArray);
window.ezoicPlaceholderArray = null;
console.log('ezoic defined and array reset');
window.ezstandalone.enable();
console.log('ezoic enabled');
window.ezstandalone.display();
console.log('ezoic displayed');
window.ezoicEnabled = true;
window.ezoicRefreshed = true;
}
// On Refresh We Have To Destroy & Refresh
if (!window.ezoicRefreshed) {
window.ezstandalone.destroy();
console.log('ezoic destroyed');
window.ezstandalone.define(window.ezoicPlaceholderArray);
window.ezoicPlaceholderArray = null;
console.log('ezoic refresh defined and array reset');
window.ezstandalone.refresh();
console.log('ezoic refreshed');
window.ezoicRefreshed = true;
}
});
} else {
console.log('Error: Missing Ezoic Standalone');
}
},
addPlaceholderOnce(array, placeholder) {
if (!array.includes(placeholder)) {
array.push(parseInt(placeholder));
console.log('ezoic-pub-ad-placeholder-' + placeholder + ' - Created');
}
},
If you need to change your placeholder setup for different reasons as I do simply add window.ezoicRefreshed = false; to the appropriate lifecycle hook. In my case I have it in beforeUnmount because each route gets a custom placeholder list.
Hopefully this helps!
I am using React with dhtmlx-gantt library to create gantt chart. I met with issue when using the filter function together with useEffect/useLayoutEffect lifecycle.
The gist of it is that the browser is not painting/rendering the correct UI on the screen on certain condition.
The start state load screen looks like this:
6 Task
After filter, this should be how it looks like:
Should be left with 5 task after filtering away duration > 4
But this is how it is:
Left with 5 task but an empty row is shown rather than "refreshing" (not sure if this is the right term to use)
I have created a github repo with different scenario describing the problem, and how to reproduce those issue. More information on how to run the sample can be found in the README.md. Please let me know if more information needs to be provided.
Sample 1: Using conditional rendering will cause issue on painting UI changes
Sample 2: Turning smart_rendering config on and off cause issue on painting UI changes
Sample 3: Calling the function within the parent component and in child component with exact same code cause issue on painting UI
My desired outcome is to able to render the UI correctly, whether or not this code to filter the data is ran on parent or child component.
I should also mention that a workaround was to use if (document.querySelector(".gantt_grid")) gantt.render(); rather than gantt.refreshData() in onBeforeTaskDisplay event which will then correctly paint the UI changes. But I still like to understand why does this happens. Is there anything I did wrongly in term of using the React lifecycle and so on.
Thank you.
Your code looks fine and should work correctly.
The issue is on dhtmlxGantt end, it has been confirmed and is now fixed in the dev branch.
The bug itself was caused by the new smart rendering mechanism introduced in v6.2.0.
It caches previously calculated positions of tasks in order to minimize calculations. In certain circumstances when a gantt instance has been initialized multiple times, it didn't invalidate that cache when it was necessary. Because of that, tasks were displayed at the same positions as they had before filtering was applied (thus the blank row where the first task has been).
In short, the issue will be fixed in the next bugfix update - v6.2.6.
If everything goes as planned, it will be released tomorrow (2019-09-19)
Try gantt.render() after gantt.refreshData() in your code:
useEffect(() => {
const onBeforeTaskDisplay = gantt.attachEvent("onBeforeTaskDisplay", function (id, task) {
console.log("filters", task.text, filter)
if (filter && task.duration > 4) {
return false;
}
return true;
});
gantt.refreshData();
gantt.render();
// This should have been here
return () => {
gantt.detachEvent(onBeforeTaskDisplay);
}
}, [filter])
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.