There will be 4 steps. With the final product that we'll make in 3 minutes, you could:
We'll use a headless browser to visit a url, take a screenshot, and then render that screenshot into our page.
Here's what our final product will look like. Let’s start 🏇🏿
Optional video tutorial (featuring me 🏇🏿) below:
pip install Django selenium Pillow webdriver_manager
django-admin startproject core .
python manage.py startapp sim
The code is setup for Google Chrome. You can use Brave or Safari, but you’ll need to modify the code (only 4-5 lines to change).
settings.py
INSTALLED_APPS
INSTALLED_APPS = [
# ...
'sim',
]`
core/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include("sim.urls")),
]
sim/urls.py
from django.urls import path
from sim.views import CaptureView, capture_page
urlpatterns = [
path('capture/', capture_page, name='capture_page'),
path('capture-image/', CaptureView.as_view(), name='capture_image'),
]
sim/views.py
:import os
from django.http import HttpResponse
from django.shortcuts import render
from django.views import View
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
class CaptureView(View):
def post(self, request, *args, **kwargs) -> HttpResponse:
url = request.POST.get('url')
print(f'{url = }')
if url:
return self.capture_website(url)
else:
return HttpResponse('No url provided', status=400)
def capture_website(self, url: str) -> HttpResponse:
"""
Visits the url to take a screenshot.
"""
chrome_options = Options()
chrome_options.add_argument("--headless")
driver = webdriver.Chrome(options=chrome_options)
driver.get(url)
print(f'Getting image for {url = }')
driver.set_window_size(1920, 1080)
# Create a path for our screenshot file.
static_dir = os.path.join('sim', 'static', 'sim')
os.makedirs(static_dir, exist_ok=True)
screenshot_path = os.path.join(static_dir, 'screenshot.png')
driver.save_screenshot(screenshot_path)
driver.quit()
return render(self.request, 'preview.html')
def capture_page(request) -> HttpResponse:
"""
Renders the initial page.
"""
return render(request, 'capture.html')
sim/templates/
preview.html
in sim/templates/
containing:{% load static %}
<style>
#preview-link img {
border-radius: 15px;
width: 80%;
margin: auto;
}
#preview-link {
display: flex;
justify-content: center;
}
</style>
<a id="download-link" href="{% static 'sim/screenshot.png' %}" download="screenshot.png">Download Image</a>
<a id="preview-link" href="{% static 'sim/screenshot.png' %}" target="_blank" style="cursor: pointer">
<img id="screenshot" src="{% static 'sim/screenshot.png' %}" alt="Website Screenshot">
</a>
- Create capture.html
in sim/templates/
containing:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Website Capture</title>
<script src="https://unpkg.com/htmx.org@1.9.6" integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni" crossorigin="anonymous"></script>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
background-color: #f0f0f0;
}
h1 {
font-weight: normal;
}
form {
margin-bottom: 20px;
}
input, button {
padding: 10px;
margin: 5px;
border: 1px solid #ccc;
border-radius: 4px;
}
button:disabled {
background-color: #ccc;
}
#loading {
display: none;
}
#preview[loading] #loading {
display: block;
}
#preview[loading] img, #preview[loading] p, #preview[loading] a {
display: none;
}
#preview {
text-align: center;
}
.htmx-indicator{
opacity:0;
transition: opacity 500ms ease-in;
}
.htmx-request .htmx-indicator{
opacity:1
}
.htmx-request.htmx-indicator{
opacity:1
}
#spinner {
position: fixed;
margin: auto;
}
#spinner:before {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 80px;
height: 80px;
margin-top: -40px;
margin-left: -40px;
border: 4px solid #f1cbcb;
border-top-color: transparent;
border-radius: 50%;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<h1>Screenshot any website</h1>
<form hx-post="/capture-image/" hx-trigger="submit" hx-swap="innerHTML" hx-target="#preview" hx-indicator="#spinner">
{% csrf_token %}
<input type="url" name="url" required placeholder="Enter URL" value="{{ url }}">
<button type="submit">Capture</button>
</form>
<div id="spinner" class="spinner htmx-indicator"></div>
<div id="preview">
<!-- Content will be replaced by the server response -->
</div>
</body>
</html>
python manage.py runserver
Now, you can visit the page at http://127.0.0.1:8000/capture/
, submit a url, and receive the screenshot back automatically.
Deploy this online and sell it as a service 💵.
If you do, I'd recommend using serverless functions when deploying this: each request will take a few seconds. Here's my guide on how to add serverless functions with Django as simply as possible: How to add serverless functions to Django in 6 minutes 🧠
Add mobile device previews by adapting the code to capture mobile views (You'll only need to change one line of the above code).
I want to release high-quality products as soon as possible. Probably like you, I want to make my Django product ideas become reality as soon as possible.
That's why I'm building Photon Designer - an entirely visual editor for building Django frontend at the speed that light hits your eyes 💡