Screenshots in Streamlit
Hello everyone, this is my first post in the forum!
Description
After a while, I managed to create a “screenshot generator” of my Streamlit App (locally developed and self deployed on AWS) via JS code injection (and a bit of HTML).
This code also includes support for iframe capture (in case you have any streamlit-elements
instanced (such as st-ag-grid
or custom elements).
As you may see in the code below, it is only working on Firefox, as Chrome cross-origin policies are far more strict impeding any chance of capturing the iframes.
I would like to share this code in case you find it helpful or you can contribute.
I am also working on providing support for Chrome-based browser and PDF generation capabilities, so feel free to share any ideas.
Code
import html
import uuid
import streamlit as st
def inject_js_code(source: str) -> None:
div_id = uuid.uuid4()
st.markdown(
f"""
<div style="display:none" id="{div_id}">
<iframe src="javascript: \
var script = document.createElement('script'); \
script.type = 'text/javascript'; \
script.text = {html.escape(repr(source))}; \
var div = window.parent.document.getElementById('{div_id}'); \
div.appendChild(script); \
div.parentElement.parentElement.parentElement.style.display = 'none'; \
"/>
</div>
""",
unsafe_allow_html=True,
)
def print_window() -> None:
# JS Code to be executed
source = r"window.print()"
inject_js_code(source=source)
def screenshot_window() -> None:
# JS Code to be executed
source = """
// Function to detect if the current browser is Chrome
const isChrome = () => /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
const button = document.getElementById('reportButton');
button.addEventListener('click', function() {
// Alert and exit if the browser is Chrome
if (isChrome()) {
alert("Currently this function is available only on Firefox!");
//button.style.display = 'none'; // Hides the button
return;
}
// Load a script dynamically and execute a callback after loading
const loadScript = (url, isLoaded, callback) => {
if (!isLoaded()) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.onload = callback;
script.src = url;
document.head.appendChild(script);
} else {
callback();
}
};
// Check if html2canvas library is loaded
const isHtml2CanvasLoaded = () => typeof html2canvas !== 'undefined';
// Capture an individual iframe and call a callback with the result
const captureIframe = (iframe, callback) => {
try {
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
html2canvas(iframeDoc.body, {
scale: 1,
logging: true,
useCORS: true,
allowTaint: true
}).then(canvas => {
callback(canvas ? canvas : null);
}).catch(error => {
console.error('Could not capture iframe:', error);
callback(null);
});
} catch (error) {
console.error('Could not access iframe:', error);
callback(null);
}
};
// Main function to capture all windows
const captureAllWindows = () => {
const streamlitDoc = window.parent.document;
const stApp = streamlitDoc.querySelector('.main > .block-container');
const iframes = Array.from(stApp.querySelectorAll('iframe'));
let capturedImages = [];
// Process each iframe sequentially
const processIframes = (index = 0) => {
if (index < iframes.length) {
captureIframe(iframes[index], function(canvas) {
if (canvas) {
const img = document.createElement('img');
img.src = canvas.toDataURL('image/png');
capturedImages.push({iframe: iframes[index], img: img});
} else {
console.error('Skipping an iframe due to capture failure.');
}
processIframes(index + 1);
});
} else {
// Capture the main app window after processing all iframes
html2canvas(stApp, {
onclone: function(clonedDocument) {
const clonedIframes = Array.from(clonedDocument.querySelectorAll('iframe'));
capturedImages.forEach(({img}, index) => {
if (index < clonedIframes.length) {
const clonedIframe = clonedIframes[index];
clonedIframe.parentNode.replaceChild(img, clonedIframe);
}
});
},
scale: 1,
logging: true,
useCORS: true,
allowTaint: true,
ignoreElements: () => {}
}).then(finalCanvas => {
// Create a download link for the captured image
finalCanvas.toBlob(blob => {
const url = window.URL.createObjectURL(blob);
var link = document.createElement('a');
link.style.display = 'none';
link.href = url;
link.download = 'screenshot.png';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
});
}).catch(error => {
console.error('Screenshot capture failed:', error);
});
}
};
processIframes();
};
loadScript(
'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.3.2/html2canvas.min.js',
isHtml2CanvasLoaded,
captureAllWindows
);
});
"""
inject_js_code(source=source)
def add_reportgen_button():
st.markdown(
"""
<button id="reportButton" class="st-style-button">Generate Page Report</button>
<style>
.st-style-button {
display: inline-flex;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
font-weight: 400;
padding: 0.25rem 0.75rem;
border-radius: 0.5rem;
min-height: 38.4px;
margin: 0px;
line-height: 1.6;
color: inherit;
width: auto;
user-select: none;
background-color: white; /* Set a white background */
border: 1px solid rgba(49, 51, 63, 0.2);
outline: none; !important
box-shadow: none !important;
}
/* Change background on mouse-over */
.st-style-button:hover {
background-color: white;
color: #0A04D2;
border: 1px solid #0A04D2;
}
</style>
""",
unsafe_allow_html=True,
)
screenshot_window()
Environment info:
streamlit == 1.30.0
python == 3.10.13