I'm using the PDF.js library to render a pdf into the canvas. That pdf has hyperlinks in there, The PDF.js library is drawing the pdf into the canvas but the hyperlinks don't work.
Any Idea if it possible that hyperlinks work into the canvas?
Thanks
Here is a fiddle that shows you how to enable annotations (including hyperlinks) in PDF files.
The original PDF file used in the fiddle is here.
I used viewer code (web/page_view.js,web/viewer.css) as refrence to write this fiddle.
HTML:
<!doctype html>
<html lang="en">
<head>
<link href="style.css" rel="stylesheet" media="screen" />
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="http://seikichi.github.io/tmp/PDFJS.0.8.715/pdf.min.js" type="text/javascript"></script>
<script src="http://seikichi.github.io/tmp/PDFJS.0.8.715/ui_utils.js"></script>
<script src="./main.js" type="text/javascript"></script>
</head>
<body>
<div id="pdfContainer" class="pdf-content">
<canvas id="the-canvas"></canvas>
<div class="annotationLayer"></div>
</div>
</body>
</html>
CSS:
body {
font-family: arial, verdana, sans-serif;
}
.pdf-content {
border: 1px solid #000000;
}
.annotationLayer > a {
display: block;
position: absolute;
}
.annotationLayer > a:hover {
opacity: 0.2;
background: #ff0;
box-shadow: 0px 2px 10px #ff0;
}
.annotText > div {
z-index: 200;
position: absolute;
padding: 0.6em;
max-width: 20em;
background-color: #FFFF99;
box-shadow: 0px 2px 10px #333;
border-radius: 7px;
}
.annotText > img {
position: absolute;
opacity: 0.6;
}
.annotText > img:hover {
opacity: 1;
}
.annotText > div > h1 {
font-size: 1.2em;
border-bottom: 1px solid #000000;
margin: 0px;
}
JavaScript:
PDFJS.workerSrc = 'http://seikichi.github.io/tmp/PDFJS.0.8.715/pdf.min.worker.js';
$(function () {
var pdfData = loadPDFData();
PDFJS.getDocument(pdfData).then(function (pdf) {
return pdf.getPage(1);
}).then(function (page) {
var scale = 1;
var viewport = page.getViewport(scale);
var $canvas = $('#the-canvas');
var canvas = $canvas.get(0);
var context = canvas.getContext("2d");
canvas.height = viewport.height;
canvas.width = viewport.width;
var $pdfContainer = $("#pdfContainer");
$pdfContainer.css("height", canvas.height + "px")
.css("width", canvas.width + "px");
var renderContext = {
canvasContext: context,
viewport: viewport
};
page.render(renderContext);
setupAnnotations(page, viewport, canvas, $('.annotationLayer'));
});
function setupAnnotations(page, viewport, canvas, $annotationLayerDiv) {
var canvasOffset = $(canvas).offset();
var promise = page.getAnnotations().then(function (annotationsData) {
viewport = viewport.clone({
dontFlip: true
});
for (var i = 0; i < annotationsData.length; i++) {
var data = annotationsData[i];
var annotation = PDFJS.Annotation.fromData(data);
if (!annotation || !annotation.hasHtml()) {
continue;
}
var element = annotation.getHtmlElement(page.commonObjs);
data = annotation.getData();
var rect = data.rect;
var view = page.view;
rect = PDFJS.Util.normalizeRect([
rect[0],
view[3] - rect[1] + view[1],
rect[2],
view[3] - rect[3] + view[1]]);
element.style.left = (canvasOffset.left + rect[0]) + 'px';
element.style.top = (canvasOffset.top + rect[1]) + 'px';
element.style.position = 'absolute';
var transform = viewport.transform;
var transformStr = 'matrix(' + transform.join(',') + ')';
CustomStyle.setProp('transform', element, transformStr);
var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px';
CustomStyle.setProp('transformOrigin', element, transformOriginStr);
if (data.subtype === 'Link' && !data.url) {
// In this example, I do not handle the `Link` annotations without url.
// If you want to handle those annotations, see `web/page_view.js`.
continue;
}
$annotationLayerDiv.append(element);
}
});
return promise;
}
});
function loadPDFData() {
/*jshint multistr: true */
var base64pdfData = '...'; //should contain base64 representing the PDF
function base64ToUint8Array(base64) {
var raw = atob(base64);
var uint8Array = new Uint8Array(new ArrayBuffer(raw.length));
for (var i = 0, len = raw.length; i < len; ++i) {
uint8Array[i] = raw.charCodeAt(i);
}
return uint8Array;
}
return base64ToUint8Array(base64pdfData);
}
Enable Text Selection in PDF.JS
Step 1: Adding a Element to Hold the Text Layer
<div id="text-layer"></div>
This div will be in addition to the element where the PDF is rendered, so the HTML will look like :
<canvas id="pdf-canvas"></canvas>
<div id="text-layer"></div>
Step 2 : Adding CSS for Text Layer
Add the following to your CSS file :
#text-layer {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow: hidden;
opacity: 0.2;
line-height: 1.0;
}
#text-layer > div {
color: transparent;
position: absolute;
white-space: pre;
cursor: text;
transform-origin: 0% 0%;
}
Step 3: Getting the PDF Text
After the PDF has been rendered in the canvas, you need to get the text contents of the PDF, and place that text in the text layer.
// page is the page context of the PDF page
// viewport is the viewport required in renderContext
// For more see https://usefulangle.com/post/20/pdfjs-tutorial-1-preview-pdf-during-upload-wih-next-prev-buttons
page.render(renderContext).then(function() {
// Returns a promise, on resolving it will return text contents of the page
return page.getTextContent();
}).then(function(textContent) {
// PDF canvas
var pdf_canvas = $("#pdf-canvas");
// Canvas offset
var canvas_offset = pdf_canvas.offset();
// Canvas height
var canvas_height = pdf_canvas.get(0).height;
// Canvas width
var canvas_width = pdf_canvas.get(0).width;
// Assign CSS to the text-layer element
$("#text-layer").css({ left: canvas_offset.left + 'px', top: canvas_offset.top + 'px', height: canvas_height + 'px', width: canvas_width + 'px' });
// Pass the data to the method for rendering of text over the pdf canvas.
PDFJS.renderTextLayer({
textContent: textContent,
container: $("#text-layer").get(0),
viewport: viewport,
textDivs: []
});
});
source: https://usefulangle.com/post/90/javascript-pdfjs-enable-text-layer
setupAnnotations = (page, viewport, canvas, annotationLayerDiv) => {
let pdfjsLib = window['pdfjs-dist/build/pdf'];
let pdfjsViewer = window['pdfjs-dist/web/pdf_viewer'];
//BELOW--------- Create Link Service using pdf viewer
let pdfLinkService = new pdfjsViewer.PDFLinkService();
page.getAnnotations().then(function (annotationsData) {
viewport = viewport.clone({
dontFlip: true
});
let pdf_canvas = canvas;
// Render the annotation layer
annotationLayerDiv.style.left = pdf_canvas.offsetLeft + 'px';
annotationLayerDiv.style.top = pdf_canvas.offsetTop + 'px';
annotationLayerDiv.style.height = viewport.height + 'px';
annotationLayerDiv.style.width = viewport.width + 'px';
pdfjsLib.AnnotationLayer.render({
viewport: viewport,
div: annotationLayerDiv,
annotations: annotationsData,
page: page,
linkService: pdfLinkService,
enableScripting: true,
renderInteractiveForms: true
});
}
IMP ---- Do not forget to add this CSS
.annotation-layer{
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow: hidden;
opacity: 1;
}
.annotation-layer section{
position: absolute;
cursor: pointer;
}
.annotation-layer section a{
display: block;
width: 100%;
height: 10px;
}
In above example, Link service instance is created using class in pdf viewer, which is being passed as parameter to annotation layer render method.
Please refer source code of PDF.js refer to /web/pdf_viewer.js - class PDFLinkService for more information.
PDF.js version - v2.9.359
Related
I am trying to transform this element into a standard web component using Lit. (https://www.w3schools.com/howto/howto_js_image_comparison.asp)
I totally new to Lit and to web components and am struggling to select elements from the shadow DOM. Right now, I am stuck with the var x inside the initComparisons() function. I am aware that the document object does not exist in the shadow dom and must be replaced by renderRoot, however, I am not sure either If I am selecting the elements the right way or what does replace the window object... Do you notice something wrong with this code? I am stuck at the first lines of the initComparisons() function as x always returns null no matter what I do....
Any help will be appreciated.
Thank you very much.
import {
LitElement,
css,
html,
} from "https://cdn.jsdelivr.net/gh/lit/dist#2/all/lit-all.min.js";
export class Comparator extends LitElement {
static properties = {
baseImage: "",
imageWidth: "",
imageHeight: "",
altImage: "",
};
// Define scoped styles right with your component, in plain CSS
static styles = css`
* {
box-sizing: border-box;
}
.img-comp-container {
position: relative;
height: 200px; /*should be the same height as the images*/
}
.img-comp-img {
position: absolute;
width: auto;
height: auto;
overflow: hidden;
}
.img-comp-img img {
display: block;
vertical-align: middle;
}
.img-comp-slider {
position: absolute;
z-index: 11;
cursor: ew-resize;
/*set the appearance of the slider:*/
width: 40px;
height: 40px;
background-color: #2196f3;
opacity: 0.7;
border-radius: 50%;
}
.border-slider {
position: absolute;
z-index: 10;
cursor: ew-resize;
/*set the appearance of the slider:*/
width: 5px;
height: 130%;
background-color: red;
opacity: 1;
}
.border-slider::after {
content: url("./separator.svg");
position: absolute;
width: 30px;
height: 30px;
background: red;
top: calc(50% - 15px);
left: calc(50% - 15px);
}
`;
constructor() {
super();
// Declare reactive defaults
this.baseImage = "https://api.lorem.space/image/house?w=800&h=600";
this.altImage = "https://api.lorem.space/image/house?w=800&h=600";
this.imageWidth = "800px";
this.imageHeight = "600px";
}
connectedCallback() {
super.connectedCallback();
this.initComparisons();
}
// Render the UI as a function of component state
render() {
return html`
<div class="img-comp-container">
<div class="img-comp-img">
<img
src="${this.baseImage}"
width="${this.imageWidth}"
height="${this.imageHeight}"
/>
</div>
<div id="img-comp-overlay" class="img-comp-img">
<img
src="${this.altImage}"
width="${this.imageWidth}"
height="${this.imageHeight}"
/>
</div>
</div>
`;
}
//HELPER FUCTIONS GO HERE
initComparisons() {
var x, i;
/*find all elements with an "overlay" class:*/
x = this.renderRoot.querySelector("#img-comp-overlay");
for (i = 0; i < x.length; i++) {
/*once for each "overlay" element:
pass the "overlay" element as a parameter when executing the compareImages function:*/
compareImages(x[i]);
}
function compareImages(img) {
var slider,
img,
clicked = 0,
w,
h;
/*get the width and height of the img element*/
w = img.offsetWidth;
h = img.offsetHeight;
/*set the width of the img element to 50%:*/
img.style.width = w / 2 + "px";
/*create slider:*/
slider = this.renderRoot.createElement("DIV");
slider.setAttribute("class", "border-slider");
/*insert slider*/
img.parentElement.insertBefore(slider, img);
/*position the slider in the middle:*/
slider.style.top = h / 2 - slider.offsetHeight / 2 + "px";
slider.style.left = w / 2 - slider.offsetWidth / 2 + "px";
/*execute a function when the mouse button is pressed:*/
slider.addEventListener("mousedown", slideReady);
/*and another function when the mouse button is released:*/
this.renderRoot.addEventListener("mouseup", slideFinish);
/*or touched (for touch screens:*/
slider.addEventListener("touchstart", slideReady);
/*and released (for touch screens:*/
window.addEventListener("touchend", slideFinish);
function slideReady(e) {
/*prevent any other actions that may occur when moving over the image:*/
e.preventDefault();
/*the slider is now clicked and ready to move:*/
clicked = 1;
/*execute a function when the slider is moved:*/
window.addEventListener("mousemove", slideMove);
window.addEventListener("touchmove", slideMove);
}
function slideFinish() {
/*the slider is no longer clicked:*/
clicked = 0;
}
function slideMove(e) {
var pos;
/*if the slider is no longer clicked, exit this function:*/
if (clicked == 0) return false;
/*get the cursor's x position:*/
pos = getCursorPos(e);
/*prevent the slider from being positioned outside the image:*/
if (pos < 0) pos = 0;
if (pos > w) pos = w;
/*execute a function that will resize the overlay image according to the cursor:*/
slide(pos);
}
function getCursorPos(e) {
var a,
x = 0;
e = e.changedTouches ? e.changedTouches[0] : e;
/*get the x positions of the image:*/
a = img.getBoundingClientRect();
/*calculate the cursor's x coordinate, relative to the image:*/
x = e.pageX - a.left;
/*consider any page scrolling:*/
x = x - window.pageXOffset;
return x;
}
function slide(x) {
/*resize the image:*/
img.style.width = x + "px";
/*position the slider:*/
slider.style.left = img.offsetWidth - slider.offsetWidth / 2 + "px";
}
}
}
}
customElements.define("image-compare", Comparator);
connectedCallback() is likely too early to call this.initComparison() as the elements might not have been populated within the shadowroot. That happens on first render so you can grab those elements in firstUpdated() instead.
I'm in such a situation where i need to wait till the image gets loaded once the image gets loaded i need to gets its computed height so that i can set the yellow color selector accordingly.
Question: based on computed height of image i'm setting yellow color selector. it works with setTimeout() randomly but i don't want such approach.
let images = ['https://via.placeholder.com/150','https://via.placeholder.com/110/0000FF/808080%20?Text=Digital.com','https://via.placeholder.com/80/0000FF/808080%20?Text=Digital.com'];
let image = `<img src="${images[Math.floor(Math.random()*images.length)]}"/>`
document.getElementById('content').innerHTML = `<div class="box">${image}</div>`;
//actual code
let height = window.getComputedStyle(document.querySelector('.box'), null).getPropertyValue('height');
let imageWidth = window.getComputedStyle(document.querySelector('.box img'), null).getPropertyValue('width');
console.log('height',height,'width',imageWidth);
wrapImage = `<div style="width:calc(${imageWidth} + 10px);height:calc(${height} + 10px);position:absolute;left:0;top:0;border:1px solid yellow;"></div>`;
document.querySelector('.box').insertAdjacentHTML('beforeend',wrapImage);
.box{
width:100%;
height:auto;
border:1px solid red;
position:relative;
}
<div id="content">
</div>
with setTimeout it works but i don't want such approach,i want callback or some event once element is ready
let images = ['https://via.placeholder.com/150','https://via.placeholder.com/110/0000FF/808080%20?Text=Digital.com','https://via.placeholder.com/80/0000FF/808080%20?Text=Digital.com'];
let image = `<img src="${images[Math.floor(Math.random()*images.length)]}"/>`
document.getElementById('content').innerHTML = `<div class="box">${image}</div>`;
//actual code
setTimeout(() => {
let height = window.getComputedStyle(document.querySelector('.box'), null).getPropertyValue('height');
let imageWidth = window.getComputedStyle(document.querySelector('.box img'), null).getPropertyValue('width');
console.log('height',height,'width',imageWidth);
wrapImage = `<div class="select" style="width:calc(${imageWidth} + 10px);height:${height};position:absolute;left:0;top:0;border:1px solid yellow;"></div>`;
document.querySelector('.box').insertAdjacentHTML('beforeend',wrapImage);
document.querySelector('.select').height = document.querySelector('.select').height + 10;
console.log('after computed height and added 10px',window.getComputedStyle(document.querySelector('.box'), null).getPropertyValue('height'));
},700);
.box{
width:100%;
height:auto;
border:1px solid red;
position:relative;
}
<div id="content">
</div>
Please help me thanks in advance !!!
The image hasn't finished loading when you retrieved the height and width. To solve this, you'll need to wait for the images to load first, then get their height and width.
Listen for the window load event, which will fire when all resources (including images) have loaded completely:
let images = ['https://via.placeholder.com/150', 'https://via.placeholder.com/110/0000FF/808080%20?Text=Digital.com', 'https://via.placeholder.com/80/0000FF/808080%20?Text=Digital.com'];
let image = `<img src="${images[Math.floor(Math.random()*images.length)]}"/>`
document.getElementById('content').innerHTML = `<div class="box">${image}</div>`;
//actual code
window.addEventListener('load', function() {
let height = window.getComputedStyle(document.querySelector('.box'), null).getPropertyValue('height');
let imageWidth = window.getComputedStyle(document.querySelector('.box img'), null).getPropertyValue('width');
console.log('height', height, 'width', imageWidth);
wrapImage = `<div class="select" style="width:calc(${imageWidth} + 10px);height:${height};position:absolute;left:0;top:0;border:1px solid yellow;"></div>`;
document.querySelector('.box').insertAdjacentHTML('beforeend', wrapImage);
document.querySelector('.select').height = document.querySelector('.select').height + 10;
console.log('after computed height and added 10px', window.getComputedStyle(document.querySelector('.box'), null).getPropertyValue('height'));
});
.box {
width: 100%;
height: auto;
border: 1px solid red;
position: relative;
}
<div id="content">
</div>
To indicate selection you can use CSS outline property. That way you won't have to manage selection dimensions yourself.
Following is demo code. As you want to do it over multiple images, I've added 3 images. And you can select multiple images.
function changeImages() {
var images = document.getElementsByTagName('img');
for (var i = 0; i < images.length; i++) {
images[i].src = "https://via.placeholder.com/" + Math.floor(Math.random() * 50 + 50).toString() + "/0a0a0a/ffffff";
}
}
function setClickEvents() {
var images = document.getElementsByTagName('img');
for (var i = 0; i < images.length; i++) {
images[i].addEventListener("click", function(event) {
event.target.classList.toggle("selected");
});;
}
}
function init() {
document.getElementById('content').innerHTML = `<div id="box"></div>`;
var box = document.getElementById('box');
var img = document.createElement("img");
img.classList.add("selected");
box.appendChild(img);
box.appendChild(document.createElement("img"));
box.appendChild(document.createElement("img"));
setClickEvents();
changeImages();
}
#content {
padding: 15px;
margin: 10px;
}
#box {
width: 100%;
height: 120px;
border: 1px dotted red;
position: relative;
}
img {
margin: 15px;
}
/* use this class for marking selected elements */
.selected {
outline: thick double #f7d205;
outline-offset: 7px;
}
<!DOCTYPE html>
<html lang="en">
<body onload="init()">
<button onclick="changeImages()">Change Images</button>
<div id="content"></div>
</body>
</html>
Click on image to toggle selection.
If you want more flexibility then you can use Resize Observer. With this when you change src attribute of image tag you'll able to change selection size.
const imageObserver = new ResizeObserver(function(entries) {
for (let entry of entries) {
var img = entry.target;
let height = img.height + 10;
let width = img.width + 10;
console.log('Added 10px. height:', height, ' width:', width);
wrapImage = `<div class="select" style="width:${width}px;height:${height}px;position:absolute;left:0;top:0;border:1px solid #f7d205;"></div>`;
document.querySelector('.box').insertAdjacentHTML('beforeend', wrapImage);
}
});
function init() {
document.getElementById('content').innerHTML = `<div id="box" class="box"></div>`;
var box = document.getElementById('box');
var img = document.createElement("img");
img.src = "https://via.placeholder.com/" + Math.floor(Math.random() * 50 + 80).toString() + "/0a0a0a/ffffff";
box.appendChild(img);
imageObserver.observe(img);
}
<!DOCTYPE html>
<html lang="en">
<style>
#box {
width: 100%;
border: 1px dotted red;
position: relative;
}
</style>
<body onload="init()">
<div id="content"></div>
</body>
</html>
Note: Using same ResizeObserver you can observe multiple images:
var images = document.getElementsByTagName('img');
for (var i = 0; i < images.length; i++) {
imageObserver.observe(images[i]);
}
Edit: As requested, demonstrating observing img resize from parent div element. Here the image is wrapped in div #box. On resize img dispatches a custom event and parent handles it.
function handleChildResize(event) {
console.log('parent: got it! handling it.. ')
var img = event.data;
let height = img.offsetHeight + 10;
let width = img.offsetWidth + 10;
console.log('Added 10px. height:', height, ' width:', width);
wrapImage = `<div class="select" style="width:${width}px;height:${height}px;position:absolute;left:0;top:0;border:2px solid #f7d205;"></div>`;
if (document.querySelector('.box > .select')) {
document.querySelector('.box > .select').remove();
}
document.querySelector('.box').insertAdjacentHTML('beforeend', wrapImage);
event.stopPropagation();
}
const imgObserver = new ResizeObserver(function(entries) {
for (let entry of entries) {
var img = entry.target;
var event = new Event('childResized');
event.data = img;
console.log("img: i am resized. Raising an event.");
img.dispatchEvent(event);
}
});
function init() {
var box = document.getElementById('box');
box.addEventListener('load', (event) => {
console.log('The page has fully loaded');
});
var img = document.createElement("img");
img.src = "https://via.placeholder.com/" + Math.floor(Math.random() * 50 + 80).toString() + "/0a0a0a/ffffff";
box.appendChild(img);
imgObserver.observe(img);
box.addEventListener('childResized', handleChildResize, true);
}
<!DOCTYPE html>
<html>
<head>
<style>
#box {
width: 100%;
padding: 10px;
border: 1px solid red;
position: relative;
}
</style>
</head>
<body onload="init()">
<div id="content">
<div id="box" class="box"></div>
</div>
</body>
</html>
You could consider adding the 'load' event listener as a callback for image loading.
Please check the example:
const image = document.getElementById('image');
const handler = () => {
alert(image.height);
};
image.addEventListener('load', handler);
<img src="https://image.shutterstock.com/z/stock-vector-sample-stamp-grunge-texture-vector-illustration-1389188336.jpg" id="image" />
I see that you want to compute the final value of the get computed style
you see. The thing is that the getComputedStyle doesn't get updated.
So just make a function to do it!
let images = ['https://via.placeholder.com/150','https://via.placeholder.com/110/0000FF/808080%20?Text=Digital.com','https://via.placeholder.com/80/0000FF/808080%20?Text=Digital.com'];'
let image = `<img src="${images[Math.floor(Math.random()*images.length)]}"/>`
document.getElementById('content').innerHTML = `<div class="box">${image}</div>`;
//actual code
setTimeout(() => {
let height;
let imageWidth;
function calculateHeightAndWidth() {
height = window.getComputedStyle(document.querySelector('.box'), null).getPropertyValue('height');
imageWidth = window.getComputedStyle(document.querySelector('.box img'), null).getPropertyValue('width');
}
calculateHeightAndWidth()
console.log('height',height,'width',imageWidth);
wrapImage = `<div class="select" style="width:calc(${imageWidth} + 10px);height:${height};position:absolute;left:0;top:0;border:1px solid yellow;"></div>`;
document.querySelector('.box').insertAdjacentHTML('beforeend',wrapImage);
document.querySelector('.select').height = document.querySelector('.select').height + 10;
calculateHeightAndWidth()
console.log('after computed height and added 10px',window.getComputedStyle(document.querySelector('.box'), null).getPropertyValue('height'));
},700);
.box{
width:100%;
height:auto;
border:1px solid red;
position:relative;
}
<div id="content">
</div>
I see that you are creating your imgNode on a fly using ternary which means you do not have it pre-created in your HTML. So for that, you can use the solution as below by creating an Image constructor.
const images = [
"https://via.placeholder.com/150",
"https://via.placeholder.com/110/0000FF/808080%20?Text=Digital.com",
"https://via.placeholder.com/80/0000FF/808080%20?Text=Digital.com"
];
const img = new Image();
img.addEventListener("load", (ev) => {
console.log(ev);
document.getElementById(
"content"
).innerHTML = `<div class="box">${ev.target}</div>`;
const height = window
.getComputedStyle(document.querySelector(".box"), null)
.getPropertyValue("height");
const imageWidth = window
.getComputedStyle(document.querySelector(".box img"), null)
.getPropertyValue("width");
console.log("height", height, "width", imageWidth);
const wrapImage = `<div style="width:calc(${imageWidth} + 10px);height:calc(${height} + 10px);position:absolute;left:0;top:0;border:1px solid yellow;"></div>`;
document.querySelector(".box").insertAdjacentHTML("beforeend", wrapImage);
});
img.src = `${images[Math.floor(Math.random() * images.length)]}`;
The following is a link which shows a nice example of playing a local video in a browser:
http://jsfiddle.net/dsbonev/cCCZ2/
<h1>HTML5 local video file player example</h1>
<div id="message"></div>
<input type="file" accept="video/*"/>
<video controls autoplay></video>
However, on top of this, I would like to allow the user to create a "trailer clip" of a particular segment of their video. In this, I would like to have some sort of adjustable playhead such as the following:
Here is a site that does this exact same thing: https://www.flexclip.com/editor/app?ratio=landscape. Any insight into how I would go about building something like this that plays the local file and allows me to select a segment of it?
There's a few different things, though the particular question seems to be how to draw a preview bar of a local video.
Local Files
From the example in your question, you can see it's not too different working with a local file than with a remote file; you can create an object URL of the local file via URL.createObjectURL and then work with it as you would a normal video link.
Drawing the Preview Bar
Drawing the preview bar should consist of seeking to the appropriate time in the video, and then drawing that frame onto a canvas. You can seek to a specific point by setting the .currentTime property of the video and waiting for the seeked event. You can then use the canvasContext.drawImage method to draw an image using the video element.
I'd also recommend using a video element that isn't attached to the DOM for this, for a better user experience that doesn't have an onscreen video jumping to different times.
It could look something like this:
function goToTime(video, time) {
return new Promise ((resolve) => {
function cb () {
video.removeEventListener('seeked', cb);
resolve();
}
video.addEventListener('seeked', cb);
video.currentTime = time;
});
}
async function drawPreviewBar(video) {
const { duration, videoWidth, videoHeight } = video;
const previewBarFrameWidth = previewBarHeight * videoWidth / videoHeight;
const previewBarFrames = previewBarWidth / previewBarFrameWidth;
for (let i = 0; i < previewBarFrames; i++) {
await goToTime(video, i * duration * previewBarFrameWidth / previewBarWidth);
previewBarContext.drawImage(video, 0, 0,
videoWidth, videoHeight,
previewBarFrameWidth * i, 0,
previewBarFrameWidth, previewBarHeight
);
}
}
Resizing / Moving the Selected Clip
If you want to create a UI to resize or move around the clip, you can use standard drag handlers.
Example
Here's a basic example:
const previewBarWidth = 600;
const previewBarHeight = 40;
const videoElement = document.getElementById('video');
const inputElement = document.getElementById('file-picker');
const invisibleVideo = document.createElement('video');
const previewBarCanvas = document.getElementById('preview-bar-canvas');
previewBarCanvas.width = previewBarWidth;
previewBarCanvas.height = previewBarHeight;
const selector = document.getElementById('selector');
const previewBarContext = previewBarCanvas.getContext('2d');
const topHandle = document.getElementById('selector-top-handle');
const leftHandle = document.getElementById('selector-left-handle');
const rightHandle = document.getElementById('selector-right-handle');
const leftMask = document.getElementById('selector-left-mask');
const rightMask = document.getElementById('selector-right-mask');
var selectorLeft = 0, selectorWidth = previewBarWidth;
var minimumPreviewBarWidth = 80; // may want to dynamically change this based on video duration
var videoLoaded = false;
function goToTime(video, time) {
return new Promise ((resolve) => {
function cb () {
video.removeEventListener('seeked', cb);
resolve();
}
video.addEventListener('seeked', cb);
video.currentTime = time;
});
}
async function drawPreviewBar(video) {
const { duration, videoWidth, videoHeight } = video;
const previewBarFrameWidth = previewBarHeight * videoWidth / videoHeight;
const previewBarFrames = previewBarWidth / previewBarFrameWidth;
for (let i = 0; i < previewBarFrames; i++) {
await goToTime(video, i * duration * previewBarFrameWidth / previewBarWidth);
previewBarContext.drawImage(video, 0, 0, videoWidth, videoHeight, previewBarFrameWidth * i, 0, previewBarFrameWidth, previewBarHeight);
}
}
function loadVideo(file) {
var src = URL.createObjectURL(file);
var loaded = new Promise(function (resolve) {
invisibleVideo.addEventListener('loadedmetadata', resolve);
}).then(function () {
videoLoaded = true;
document.body.classList.add('loaded');
return drawPreviewBar(invisibleVideo);
});
videoElement.src = src;
invisibleVideo.src = src;
return loaded;
}
function updateSelectorBar() {
selector.style.width = selectorWidth + 'px';
selector.style.left = selectorLeft + 'px';
leftMask.style.width = selectorLeft + 'px';
rightMask.style.left = (selectorLeft + selectorWidth) + 'px';
rightMask.style.width = (previewBarWidth - selectorWidth - selectorLeft) + 'px';
}
function selectorUpdated() {
if (!videoLoaded) {
return;
}
var startFraction = selectorLeft / previewBarWidth;
var durationFraction = selectorWidth / previewBarWidth;
var startTime = startFraction * invisibleVideo.duration;
var duration = durationFraction * invisibleVideo.duration;
var endTime = startTime + duration;
// do something with startTime, endTime, and duration, maybe;
videoElement.currentTime = startTime;
}
function addDragHandler (event, cb, ecb) {
var startX = event.clientX;
function dragged(e) {
cb(e.clientX - startX);
}
window.addEventListener('mousemove', dragged);
window.addEventListener('mouseup', function ended() {
window.removeEventListener('mousemove', dragged);
window.removeEventListener('mouseup', ended);
ecb();
});
}
updateSelectorBar();
topHandle.addEventListener('mousedown', function (e) {
var startLeft = selectorLeft;
addDragHandler(e, function (dx) {
selectorLeft = Math.max(0, Math.min(previewBarWidth - selectorWidth, startLeft + dx));
updateSelectorBar();
}, selectorUpdated);
});
leftHandle.addEventListener('mousedown', function (e) {
var startLeft = selectorLeft;
var startWidth = selectorWidth;
addDragHandler(e, function (dx) {
selectorLeft = Math.max(0, Math.min(selectorLeft + selectorWidth - minimumPreviewBarWidth, startLeft + dx));
selectorWidth = (startWidth + startLeft - selectorLeft);
updateSelectorBar();
}, selectorUpdated);
});
rightHandle.addEventListener('mousedown', function (e) {
var startWidth = selectorWidth;
addDragHandler(e, function (dx) {
selectorWidth = Math.max(minimumPreviewBarWidth, Math.min(previewBarWidth - selectorLeft, startWidth + dx));
updateSelectorBar();
}, selectorUpdated);
});
var pendingLoad = Promise.resolve();
inputElement.addEventListener('change', function () {
let file = inputElement.files[0];
pendingLoad = pendingLoad.then(function () {
return loadVideo(file)
});
});
#video {
width: 100%;
height: auto;
}
#preview-bar {
position: relative;
margin-top: 10px;
}
.canvas-container {
position: relative;
left: 5px;
}
#preview-bar-canvas {
position: relative;
top: 2px;
}
#selector {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100%;
border: 2px solid orange;
border-left-width: 5px;
border-right-width: 5px;
border-radius: 3px;
overflow: show;
opacity: 0;
transition: opacity 1s;
pointer-events: none;
}
.loaded #selector{
opacity: 1;
pointer-events: initial;
}
#selector-top-handle {
position: absolute;
top: 0;
height: 10px;
width: 100%;
max-width: 30px;
left: 50%;
transform: translate(-50%,-100%);
background: darkgreen;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
cursor: move;
}
.resize-handle {
position: absolute;
top: 0;
bottom: 0;
width: 5px;
cursor: ew-resize;
background: darkgreen;
opacity: 0.75;
transition: opacity 1s;
}
.resize-handle:hover {
opacity: 1;
}
.preview-mask {
position: absolute;
background: black;
opacity: 0.6;
top: 0;
bottom: 0;
}
#selector-left-mask {
left: 0;
}
#selector-left-handle {
left: -5px;
}
#selector-right-handle {
right: -5px;
}
<input type="file" id="file-picker" />
<video id="video"></video>
<div id="preview-bar">
<div class="canvas-container">
<canvas id="preview-bar-canvas"></canvas>
<div id="selector-left-mask" class="preview-mask"></div>
<div id="selector-right-mask" class="preview-mask"></div>
</div>
<div id ="selector">
<div id="selector-top-handle"></div>
<div id="selector-left-handle" class="resize-handle"></div>
<div id="selector-right-handle" class="resize-handle"></div>
</div>
</div>
Here is some infomation I found online about this question:
(all of the lines are commented fyi)
https://gist.github.com/simonhaenisch/116010ed657f6b257246464e33719613
I have some script on JS, it change width and height of canvas element:
function RefreshSizes (canvas) {
var temp_width = 320;
var temp_height = 240;
document.getElementById(canvas).setAttribute('width', temp_width);
document.getElementById(canvas).setAttribute('height', temp_height);
}
this functions calling right after canvas initializing.
It works fine on Chrome.
But in FireFox 49 I see that:
What could it be?
UPD#1 Tested code of BukkitmanPlays MCPE
UPD#2
Full CSS for canvas:
element {
width: 320px;
height: 240px;
}
.canvas {
border: 3px solid #E0E0E0;
z-index: 0;
position: relative;
}
html {
font: 10px/10px arial;
}
some code on one browser isn't the same on another browser, so in this case, what I would do:
function RefreshSizes (canv) {
var temp_width = 320;
var temp_height = 240;
var canvas = canv;
canvas.width = temp_width;
canvas.height = temp_height;
}
I'm sure this will work
I want to clone the images of the div leftSide into the div rightSide. On each click on the body the images on the left div should be cloned into the right div. But I can't get the result with the code I'm doing. Is there any mistake in my code? I want to use JavaScript.
Here's my code:
var theLeftSide = document.getElementById("leftSide");
var width = 500; var height = 500;
top_position = 0; var left_position = 0,
var numberOfFaces = 5;
var theRightSide = document.getElementById("rightSide");
var leftSideImages = theLeftSide.cloneNode(true);
document.getElementById("rightSide").appendChild(leftSideImages);
for (var i = 0; i < 5; i++) {
createElement(i);
numberOfFaces += 5;
function createElement() {
var image = document.createElement('img');
image.src = "smile.png";
image.style.position = 'absolute';
image.style.top = top_position + "px";
image.style.left = left_position + "px";
theLeftSide.appendChild(image);
top_position = Math.random() * 500 ;
left_position = Math.random() * 500 ;
Here is a simple example that clones an HTMLElement in vanilla javascript:
additional information can be found in MDN
function CloneCtrl() {
'use strict';
var self = this;
self.source = document.querySelector('.source');
self.target = document.querySelector('.target');
self.cloneSource = function(event) {
var clone = self.source.cloneNode(true);
self.target.appendChild(clone);
}
document
.getElementById('cloneBtn')
.addEventListener('click', self.cloneSource)
;
}
document.addEventListener('DOMContentLoaded', CloneCtrl);
.source {
background: lightseagreen;
width: 100px;
height: 100px;
line-height: 100px;
overflow: hidden;
margin: 5px;
display: inline-block;
text-align: center;
color: #fff;
}
.target {
border: 1px solid lightcoral;
min-height: 110px;
}
<div><button id="cloneBtn">Clone Source</button></div>
<div class="source">SOURCE</div>
<hr>
<div class="target"></div>
With jQuery, you can do it kike that:
$('#div2').html($('#div1').html());
which is found from this question: Copy the content of a div into another div.
You don't actually provide many details, thus the best I can post, hope it helps!!
you could simply try this if you have second div on page.
document.getElementById("SecondDv").innerHTML = document.getElementById("FirstDv").innerHTML;
This will copy whatever is there in FirstDiv to Second. lmk if it works.