Generating live website thumbnail previews using an Iframe
15 Mar 2015I was looking for a good way to generate website thumbnail previews for the end-user which would load near instantly. With a quick Google search I came across services that can take a screenshot but they were neither free or fast. So I set out to create my own home brewed solution which looked something like this:
Iβm going to use KnockoutJS which is a MVVM JavaScript framework for the purpose of this demo just for the convenience factor. You can do this in plain old JavaScript as well if you wanted to.
TL;DR: Scroll to the bottom for a link to the demo
HTML:
<div class="live-example">
<input data-bind='value: myURL' />
<button data-bind='click: initLinkPreview'>Show Preview</button>
<!-- ko if: showPreview -->
<iframe class="link-preview" data-bind="attr: {target: '_top', src: myURL}" sandbox='' scrolling="no" />
<!-- /ko -->
</div>
Key part to note in the HTML is that we are loading the iFrame in a sandbox mode to make sure it loads the link with a white-list of restrictions to mitigate any risk of malicious activity.
CSS:
.live-example {
height: 200px !important;
overflow: hidden;
}
.link-preview {
width: 1028px;
height: 800px;
border: none;
pointer-events: none;
-webkit-transform: scale(0.2);
-moz-transform: scale(0.2);
-ms-transform: scale(0.2);
transform: scale(0.2);
-webkit-transform-origin: 0 0;
-moz-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
The main crux of this technique comes down to the transform
CSS property which will allow us to render the URL in a full sized iFrame and then scale it down to a smaller size to make it look like a thumbnail. Also as you can see, the container class .live-example
has a height restriction of 200px to hide the space that the browser will initially occupy to render a full-sized iFrame.
RoR:
def get_headers
meta = open(params[:url]) { |f| f.meta }
render :json => {status: "success", headers: meta}
end
I also created a back-end endpoint (in this case in RoR) to return the website headers to the front-end. This is to allow the front-end to detect if the site which we are loading allows itself to be rendered in an iFrame. Lot of mainstream sites such as Google and Yahoo explicitly set X-Frame-Options
header to avoid Clickjacking attacks, etc. By doing this we can control the UX and make sure we either show a default thumbnail or hide the area in which we were supposed to render the iFrame in case the X-Frame-Options
header was set to DENY
or SAMEORIGIN
.
JavaScript:
var MyViewModel = function MyViewModel() {
var self = this;
self.myURL = ko.observable();
self.showPreview = ko.observable();
self.initLinkPreview = function () {
$.ajax({
type: "POST",
url: "/get_headers/url=?" + self.myURL(),
success: function (data) {
var headers = data.headers;
if (headers['x-frame-options'] && (headers['x-frame-options'].toUpperCase() == 'DENY' || headers['x-frame-options'].toUpperCase() == 'SAMEORIGIN')) {
self.showPreview(false);
} else {
self.showPreview(true);
}
}
});
};
};
ko.applyBindings(new MyViewModel());
So lastly the JS ties everything together. It grabs the URL from the front-end, checks with back-end to see if itβs kosher to render it in an iFrame and then either shows or hides the iFrame with that URL.