Whether it is for building a third-party service or a personal portfolio, we often find the need to present screenshots of websites in a presentable manner. In this tutorial, we learn how we can dynamically generate and frame website screenshots.
The final project can be viewed on Codesandbox.
You can find the full source code on my Github repository.
Entry-level knowledge of HTML, CSS, and JavaScript is required to follow along with this tutorial. Knowledge of Vue.Js is not required but recommended.
Nuxt.Js is an intuitive Vue.Js framework that boasts improved developer productivity and end-user experience. To easily set up the project, ensure you have installed Yarn package manager or Node package manager(npm) v5.2+ or v6.1+. This will ensure that npx is also installed. We can now use the create-nuxt-app utility:
yarn create nuxt-app nuxtjs-website-screenshot-framer
# OR
npx create-nuxt-app nuxtjs-website-screenshot-framer
# OR
npm init nuxt-app nuxtjs-website-screenshot-framer
Code language: PHP (php)
This will result in a series of prompts to customize your project to your needs. Here are our recommended defaults:
Project name: nuxtjs-website-screenshot-framer
Programming language: JavaScript
Package manager: Yarn
UI framework: Tailwind CSS
Nuxt.js modules: N/A
Linting tools: N/A
Testing framework: None
Rendering mode: Universal (SSR/SSG)
Deployment target: Server (Node.js hosting)
Development tools: N/A
What is your GitHub username:
<your-github-username>
Version control system: Git
Once the setup is complete, you may now enter and run your project:
cd nuxtjs-website-screenshot-framer
yarn dev
Once the project is set up, we will now add the Cloudinary’s recommended Nuxt.Js plugin: @nuxtjs/cloudinary. Cloudinary is a media management platform that allows us to make the most of our media assets through its comprehensive platform, APIs and SDKs.
To install it in our project, open the terminal in the project folder and run the following command:
yarn add @nuxtjs/cloudinary
# OR
npm install @nuxtjs/cloudinary
Code language: CSS (css)
Once installation is done, let us now add it to the modules
section of our nuxt.config.js
file:
// nuxt.config.js
export default {
...
modules: [
'@nuxtjs/cloudinary'
]
...
}
Code language: JavaScript (javascript)
We will now configure our Cloudinary instance by adding a cloudinary
section in our nuxt.config.js
file:
export default {
...
cloudinary: {
cloudName: process.env.NUXT_ENV_CLOUDINARY_CLOUD_NAME,
useComponent: true,
secure: true
}
}
Code language: JavaScript (javascript)
Environmental variables are values that we consume inside our codebase but configure them outside of our code in environmental configuration files. We use them for values dependent on the environment the code is deployed in or for sensitive secrets we do not want to be shared in our code repository.
To set the NUXT_ENV_CLOUDINARY_CLOUD_NAME
environmental variable, we will first create an environmental variable.
touch .env
Code language: CSS (css)
We will then set the value in the .env
file
<!-- .env -->
NUXT_ENV_CLOUDINARY_CLOUD_NAME=<top-secret-cloud-name>
Code language: HTML, XML (xml)
To obtain a Cloudinary cloud name, ensure you have an account by signing up. You can then retrieve your cloud name conveniently on your account dashboard.
Instead of making our users upload their screenshots, we will generate them on their behalf. First, we will create a simple form to receive the website URL:
<!-- pages/index.vue -->
<form @submit.prevent="uploadScreenshots">
...
<div>
<div>
<label for="url"> Website url (https://example.com) </label>
<div>
<input
type="text"
name="url"
id="url"
v-model="url"
/>
</div>
</div>
</div>
<div>
<p v-if="uploading" >
Generating framed screenshots...
</p>
<div v-else>
<button type="reset">Cancel</button>
<button type="submit">Generate</button>
</div>
</div>
</form>
Code language: HTML, XML (xml)
In the above, we will save the value of the URL in the rule state variable. We will then trigger the
upload screenshots method when the form is submitted. When the uploading
variable is true, we will hide the form submission buttons and display the “Generating framed screenshots…” message.
We will use a service called api flash to dynamically generate our screenshots. Feel free to register to obtain your own access key. Add this access key to the .env
file
<!-- .env -->
...
NUXT_ENV_API_FLASH_KEY=<secret-api-flash-access-key>
Code language: HTML, XML (xml)
Once the access key is configured, we will then create mobileScreenshotUrl
, tabletScreenshotUrl
, desktopScreenshotUrl
computed properties which will return the corresponding screenshots through the ApiFlash api. You may make use of the query builder to explore how to build the URLs. We make use of the rule which is submitted from the form as well as the
sizes` state variable which contains our screen sizes.
// pages/index.vue
export default {
data() {
return {
...
sizes: {
mobile: {
width: 750,
height: 1334,
},
tablet: {
width: 1620,
height: 2160,
},
desktop: {
width: 1920,
height: 1080,
},
},
...
};
},
computed: {
mobileScreenshotUrl() {
let url = `https://api.apiflash.com/v1/urltoimage?access_key=${process.env.NUXT_ENV_API_FLASH_KEY}`;
url += `&height=${this.sizes.mobile.height}`;
url += `&url=${this.url}`;
url += `&width=${this.sizes.mobile.width}`;
return url;
},
tabletScreenshotUrl() {
let url = `https://api.apiflash.com/v1/urltoimage?access_key=${process.env.NUXT_ENV_API_FLASH_KEY}`;
url += `&height=${this.sizes.tablet.height}`;
url += `&url=${this.url}`;
url += `&width=${this.sizes.tablet.width}`;
return url;
},
desktopScreenshotUrl() {
let url = `https://api.apiflash.com/v1/urltoimage?access_key=${process.env.NUXT_ENV_API_FLASH_KEY}`;
url += `&height=${this.sizes.desktop.height}`;
url += `&url=${this.url}`;
url += `&width=${this.sizes.desktop.width}`;
return url;
},
...
},
...
};
Code language: JavaScript (javascript)
We now need a method of reading the data from the URLs above and returning a base64 encoded string. We will make use of FileReader.readAsDataURL() in a method we call toDataURL
// pages/index.vue
export default {
...
methods: {
...
toDataURL(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
var reader = new FileReader();
reader.onloadend = function () {
callback(reader.result);
};
reader.readAsDataURL(xhr.response);
};
xhr.open("GET", url);
xhr.responseType = "blob";
xhr.send();
},
...
},
};
Code language: JavaScript (javascript)
We will then use this method to read the screenshot data and upload it to Cloudinary. We will set uploading
to true for 5 seconds, upload the screenshots and store the Cloudinary instances into the screenshots
object. We make use of the domain
computed property to obtain the raw website domain without any protocols or query strings. We then upload to the nuxtjs-website-screenshot-framer/${this.domainName}
folder. We make use of an upload preset in our upload. This is a predetermined set of preferences to be applied to our uploads. To create an upload preset, proceed to the Add upload preset page. Here are our recommended defaults:
Name: default-preset
Mode: unsigned
Unique filename: true
Delivery type: upload
Access mode: public
// pages/index.vue
export default {
data() {
return {
uploading: false,
...
screenshots: {
mobile: null,
tablet: null,
desktop: null,
},
...
};
},
computed:{
...
domainName() {
return this.url
.replace("http://", "")
.replace("https://", "")
.replace("www", "")
.split("/")[0];
},
}
methods: {
async uploadScreenshots(e) {
this.uploading = true;
setTimeout(() => (this.uploading = false), 5000);
this.toDataURL(
this.mobileScreenshotUrl,
async (dataUrl) =>
(this.screenshots.mobile = await this.$cloudinary.upload(dataUrl, {
public_id: "mobile",
upload_preset: "default-preset",
folder: `nuxtjs-website-screenshot-framer/${this.domainName}`,
}))
);
this.toDataURL(
this.tabletScreenshotUrl,
async (dataUrl) =>
(this.screenshots.tablet = await this.$cloudinary.upload(dataUrl, {
public_id: "tablet",
upload_preset: "default-preset",
folder: `nuxtjs-website-screenshot-framer/${this.domainName}`,
}))
);
this.toDataURL(
this.desktopScreenshotUrl,
async (dataUrl) =>
(this.screenshots.desktop = await this.$cloudinary.upload(dataUrl, {
public_id: "desktop",
upload_preset: "default-preset",
folder: `nuxtjs-website-screenshot-framer/${this.domainName}`,
}))
);
},
...
},
};
Code language: JavaScript (javascript)
Before proceeding, we need to setup the correct frames. Add the following frames to nuxtjs-website-screenshot-framer/templates
folder in your Cloudinary media library with the corresponding file names / public ids:
We will then transform the screenshots to be overlayed by the frames. This is by utilizing the overlay transformation specifying the y
offset, gravity
to dictate where the offset will apply from as well as the width
of the overlay layer. Overlay public_id
s should not have forward slashes (/
). In case of any directories, we should use colons (:
) instead. Hence the use of replaceAll method: .replaceAll('/', ':')
<!-- pages/index.vue -->
<div v-if="screenshots.mobile && screenshots.tablet && screenshots.desktop">
<div id="framed-screenshots" >
<cld-image :public-id="templates.mobile" width="400">
<cld-transformation
:overlay="screenshots.mobile.public_id.replaceAll('/', ':')"
width="380"
y="-20"
/>
</cld-image>
<cld-image :public-id="templates.tablet" width="400">
<cld-transformation
:overlay="screenshots.tablet.public_id.replaceAll('/', ':')"
width="700"
/>
</cld-image>
<cld-image :public-id="templates.desktop" width="400">
<cld-transformation
:overlay="screenshots.desktop.public_id.replaceAll('/', ':')"
width="2550"
gravity="north"
y="125"
/>
</cld-image>
</div>
</div>
Code language: HTML, XML (xml)
To enable our users to download their framed screenshots, we will create a download
method. This method will simply get the framed-screenshots
element, search for any images inside and add the image URLs (src
) to the framedScreenshots
array.
// pages/index.vue
export default {
data() {
return {
...
framedScreenshots: [],
};
},
...
methods: {
...
download() {
const container = document.getElementById("framed-screenshots");
const images = container.getElementsByTagName("img");
for (let the image of images) {
this.framedScreenshots.push(image.src);
}
},
},
};
Code language: JavaScript (javascript)
We will then expose a Download button in our user interface to trigger the download
method as well as render the generated URLs.
<!-- pages/index.vue -->
...
<button type="button" @click="download" v-if="framedScreenshots.length < 1" >
Download
</button>
<a
v-for="(screenshot, index) in framedScreenshots"
:key="index"
:href="screenshot"
target="_blank"
>
Screenshot No.{{ index + 1 }}
</a>
...
Code language: HTML, XML (xml)
Now the user can download their framed screenshots conveniently.
With this tutorial, we now enable our users to dynamically generate and frame screenshots. This is just the tip of the iceberg of what is truly possible with the Cloudinary API and SDK. Feel free to review their comprehensive image and video transformation documentation to see what is truly possible.