I'm trying to create a small and interactive image gallery inside my page and everything besides this problem is working fine: When I mouse over the images they change opacity and size, as expected, however, after I click one of them and enlarge it, nothing happens when I try mousing over them again.
This is what I mean:
Current CSS:
#houseImages img {
opacity: 0.7;
margin: 1em;
transition: 0.3s;
width: 18em;
height: 15em;
border-radius: 3em;
border-style: solid;
border-width: 0.05em;
}
#houseImages img:hover {
transform: scale(1.1);
opacity: 1.0;
cursor: pointer;
}
Current script that is being used when an image is clicked:
"use strict";
function defaultSettings(imgElement)
{
imgElement.style.position = "initial";
imgElement.style.transform = "scale(1.0)";
imgElement.style.opacity = "0.7";
}
function clickedSettings(imgElement)
{
imgElement.style.position = "absolute";
imgElement.style.transform = "scale(2.6)";
imgElement.style.left = "40%";
imgElement.style.top = "30%";
imgElement.style.opacity = "1.0";
imgElement.style.zIndex = "99";
}
function imageClicked(imgID)
{
let img = document.getElementById("img" + imgID);
let otherImages = document.querySelectorAll("#houseImages img:not(#img" + imgID + ")");
let div = document.getElementById("houseImages");
let previousCloseImageButton = document.querySelector("#houseImages button");
// if another image was already clicked there's some cleanup to do
if(previousCloseImageButton)
previousCloseImageButton.remove();
otherImages.forEach(image => defaultSettings(image));
clickedSettings(img);
let closeImageButton = document.createElement('button');
closeImageButton.setAttribute("id","close");
closeImageButton.setAttribute("onclick","notClicked(" + imgID + ")");
closeImageButton.innerHTML = 'X';
div.append(closeImageButton);
}
function notClicked(imgID)
{
let img = document.getElementById("img" + imgID);
let closeImageButton = document.querySelector("#houseImages button");
defaultSettings(img);
closeImageButton.remove();
}
When you call defaultSettings(img); in the notClicked(imgID) function, you are setting a style property directly on the HTML img objects, as if you have written <img style="opacity: 0.7"> in HTML. Styles set on an object always have higher priority than those resulting from a stylesheet. In effect, rules defined for #houseImages img:hover cannot affect your image.
What you need to do is unset a local style property to let the stylesheet work again:
function defaultSettings(imgElement)
{
imgElement.style.position = "";
imgElement.style.transform = "";
imgElement.style.opacity = "";
}
Related
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)]}`;
I'm looking to fade in and out my background image when hovering over a button on my website. I'm able to get the background image to display with the correct properties but I'm not able to figure out how to fade it in and out (to make the image feel smooth) and how to fade the all boxes other than the one thats currently hovered over. If there's any advice I can get it'd be greatly appreciated!
Codepen: https://codepen.io/chriskaram/pen/ZXjjqj
Site: https://mydietgoal.com/mydietgoal-features-and-plans
document.addEventListener('DOMContentLoaded', function() {
var btn = document.getElementById('btn1'),
outerContainer = document.getElementsByClassName('Main-content')[0];
var btnTwo = document.getElementById('btn2'),
outerContainer2 = document.getElementsByClassName('Main-content')[0];
var btnThree = document.getElementById('btn3'),
outerContainer3 = document.getElementsByClassName('Main-content')[0];
btn.addEventListener('mouseenter', hover);
btn.addEventListener('mouseleave', noHover);
btnTwo.addEventListener('mouseenter', hover2);
btnTwo.addEventListener('mouseleave', noHover2);
btnThree.addEventListener('mouseenter', hover3);
btnThree.addEventListener('mouseleave', noHover3);
function hover() {
outerContainer.style.backgroundImage = 'url(https://static1.squarespace.com/static/59a7820e2994ca11766093d3/t/59dbd720f5e2317170edb5bf/1507579681913/vegetables-fresh-healthy-food-my-diet-goal-hd.jpg)';
outerContainer.style.backgroundAttachment = "fixed";
outerContainer.style.backgroundPosition = "bottom";
outerContainer.style.backgroundRepeat = "no-repeat";
outerContainer.style.transition = "opacity .25s ease-in";
}
function hover2() {
outerContainer2.style.backgroundImage = 'url(https://static1.squarespace.com/static/59a7820e2994ca11766093d3/t/59dbd7358fd4d2e11c887fc1/1507579706733/deadlift-workout-compound-work-hard-my-diet-goal-hd.jpg)';
outerContainer.style.backgroundAttachment = "fixed";
outerContainer.style.backgroundPosition = "bottom";
outerContainer.style.backgroundRepeat = "no-repeat";
outerContainer.style.transition = "opacity .25s ease-in";
}
function hover3() {
outerContainer3.style.backgroundImage = 'url(https://static1.squarespace.com/static/59a7820e2994ca11766093d3/t/59dbd7514c0dbffb014a14c0/1507579730115/strong-powerful-motivation-healthy-body-my-diet-goal-hd.jpg)';
outerContainer.style.backgroundAttachment = "fixed";
outerContainer.style.backgroundPosition = "bottom";
outerContainer.style.backgroundRepeat = "no-repeat";
outerContainer.style.transition = "opacity .25s ease-in";
}
function noHover() {
outerContainer.style.backgroundImage = '';
}
function noHover2() {
outerContainer2.style.backgroundImage = '';
}
function noHover3() {
outerContainer3.style.backgroundImage = '';
}
});
You won't be able to fade a background image in and out separately from the element's content, but you can place the image in its own element that is behind all the other content in the element and then fade that element that contains the image:
var button = document.querySelector(".button");
var back = document.getElementById("backImg");
// Set up event handlers to change the opacity of the
// image container when mousing in and out:
button.addEventListener("mouseover", function(){
back.style.opacity = 0;
});
button.addEventListener("mouseout", function(){
back.style.opacity = 1;
});
.main {
text-align:center;
font-size:3em;
font-weight:bold;
color:#ff0;
}
#backImg {
position:absolute; /* Allow the image container to be placed into its own layer */
z-index:-1; /* Make sure that container is behind other content */
transition:all 1s; /* Configure all property changes to transition over 1 second */
}
<div class="main">
<div id="backImg">
<img src="http://www.planwallpaper.com/static/images/23-3d-beach-sand-wallpaper.jpg">
</div>
<button class="button">Hover over me!</button>
</div>
If you have an element whose height is animating using a CSS transition, is there a way to use jQuery or pure Javascript to get its finished height before the transition completes?
Consider the following example: https://jsfiddle.net/qm6zz0kq/
<div id="test"></div>
<style>
#test {
width: 100px;
height: 0;
transition: height 2s ease-in-out;
background: #F00;
}
#test.showing {
height: 100px;
}
</style>
<script>
var testElement = document.getElementById('test');
setTimeout(function() {
testElement.className = 'showing';
}, 100);
setInterval(function() {
testElement.innerHTML = 'Height: ' + testElement.clientHeight;
}, 100);
</script>
How could you modify the interval so it always generates "Height: 100"?
I've considered doing some kind of jQuery clone that doesn't have to transition and measuring its height but in this instance, the CSS is nested enough that I'd have to clone basically of the element's parents to make sure it's correct and that could be expensive.
You can put another hidden div (hidden-test, as an example) that is the same as the div test and add to it the class showing right away (without timeout), then get its height, that will be the same.
Look here an example: https://jsfiddle.net/qm6zz0kq/1/
You could read the actual CSSRule, note though this would just get the value defined in the CSS. For instance if the height was specified as 70% it would give 70% and not the actual px height it would end up as, eg if parents height was 170px, it wouldn't give you the value of 70% of 170px. Also note this will not work if the stylesheet is include from a file <link href="css.css">
var testElement = document.getElementById('test');
setTimeout(function() {
testElement.className = 'showing';
}, 100);
setTimeout(function() {
var rule = getRule("#test.showing");
if(rule){
testElement.innerHTML = 'Height: ' + rule.style.height;
}
}, 100);
function getRule(selector) {
var foundRule = null;
[].slice.call(window.document.styleSheets)
.filter(sheet=>sheet.rules || sheet.cssRules).forEach(sheet=>{
foundRule = foundRule || [].slice.call(sheet.rules||sheet.cssRules)
.filter(rule=>rule.selectorText == selector);
});
if(foundRule && foundRule[0]) return foundRule[0];
}
#test {
width: 100px;
height: 0;
transition: height 2s ease-in-out;
background: #F00;
}
#test.showing {
height: 100px;
}
<div id="test"></div>
You could also put in an element that is a clone. You do not have to also clone the parents like you mention in your question. You just have to insert the element into the same parent. This particular example uses display:none to hide the element, the returned value will not be a calculated value. Again like above if the parent's height is 400px and the height of the element is 75%, 100px will not be returned, 75% would be.
var clone = testElement.cloneNode();
//remove transition so we can get end height
clone.style.transition = "none";
//display:none so we do not have to see the temp element
clone.style.display = "none";
clone.classList.add("showing");
testElement.parentNode.appendChild(clone);
var endHeight = window.getComputedStyle(clone).height;
var testElement = document.getElementById('test');
setTimeout(function() {
testElement.className = 'showing';
}, 100);
//Clone the element
var clone = testElement.cloneNode();
//remove transition so we can get end height
clone.style.transition = "none";
//display:none so we do not have to see the temp element
clone.style.display = "none";
clone.classList.add("showing");
testElement.parentNode.appendChild(clone);
var endHeight = window.getComputedStyle(clone).height;
//Remove it as we dont need it anymore
clone.remove();
setTimeout(function() {
testElement.innerHTML = 'Height: ' + endHeight;
}, 300);
#parent {
height:300px;
}
#test {
width: 100px;
height: 0;
transition: height 2s ease-in-out;
background: #F00;
}
#test.showing {
height: 70%;
}
<div id="parent">
<div id="test"></div>
</div>
If you want the actual calculated height you would need to change the clone to use a couple different stles.
visibility:hidden to hide it instead of display:none as display will make it so we won't get a calculated value.
position:absolute to prevent it from modifying the parents dimensions
clone.style.visibility = "hidden";
clone.style.position = "absolute";
//needed to make sure element is contained by parent
parent.style.position = parent.style.position || "relative";
var endHeight = window.getComputedStyle(clone).height;
var testElement = document.getElementById('test');
setTimeout(function() {
testElement.className = 'showing';
}, 100);
//Clone the element
var clone = testElement.cloneNode();
//remove transition so we can get end height
clone.style.transition = "none";
clone.style.visibility = "hidden";
clone.style.position = "absolute";
clone.classList.add("showing");
var parent = testElement.parentNode;
parent.style.position = parent.style.position || "relative";
parent.appendChild(clone);
var endHeight = window.getComputedStyle(clone).height;
//Remove it as we dont need it anymore
clone.remove();
setTimeout(function() {
testElement.innerHTML = 'Height: ' + endHeight;
}, 300);
#parent {
height:300px;
}
#test {
width: 100px;
height: 0;
transition: height 2s ease-in-out;
background: #F00;
}
#test.showing {
height: 70%;
}
<div id="parent">
<div id="test"></div>
</div>
You can add an 'animationend' event listener to the element .
Example :
testElement.addEventListener('animationend' , showNewHeight);
showNewHeight function(){
// show new height ...do something after animation .
}
Source : http://www.w3schools.com/jsref/event_animationend.asp
hope this helps..
function showHome() {
removeSlideShow();
var homeHeader = document.createElement("div");
homeHeader.setAttribute("id", "homeHeader");
document.getElementById("window").insertBefore(homeHeader, document.getElementById("content"));
var slideShowDiv = document.createElement("div");
var images = ["slideShow/slideShow-1.jpg", "slideShow/slideShow-2.jpg", "slideShow/slideShow-3.jpg", "slideShow/slideShow-4.jpg", "slideShow/slideShow-5.jpg", "slideShow/slideShow-6.jpg", "slideShow/slideShow-7.jpg"];
homeHeader.appendChild(slideShowDiv);
startSlideShow(slideShowDiv, images);
content.innerHTML = "";
}
function startSlideShow(element, images) {
var iterator = 0;
element.setAttribute("id", "slideShowDiv");
element.setAttribute("style", "background-image: url(" + images[0] + ")");
var startInterval = setInterval(function() {
iterator++;
if (iterator == images.length) iterator = 0;
element.setAttribute("style", "background-image: url(" + images[iterator] + ")");
element.style = "background-image: url(" + images[iterator] + ")";
transition(element);
}, 3000);
}
function removeSlideShow() {
if (document.getElementById("homeHeader")) {
document.getElementById("window").removeChild(document.getElementById("homeHeader"));
}
}
function transition(element) {
element.setAttribute("style", "opacity:0.01;");
var i = 0;
var set = setInterval(function() {
i += 0.01;
element.setAttribute("style", "opacity:" + i + ";");
}, 4);
setTimeout(function() {
clearInterval(set);
element.setAttribute("style", "opacity:1;");
}, 500);
}
div#homeHeader {
background-color: #FFF;
width: 900px;
height: 280px;
border: solid 2px #F00;
border-radius: 20px;
}
div#slideShowDiv {
background-image: url(slideShow/slideShow-1.jpg);
background-color: #FFF;
width: 898px;
height: 278px;
border: solid 1px #FFF;
border-radius: 20px;
background-size: 100% 100%;
}
What i want to do is change the background image every 3 seconds. The code work but it's not changing the image, stays at 'slideShow-1.jpg'. If i remove the transition(element); part, the image rotate just fine. What should i do to get it work? Im still beginner in Javascript, will learn jquery when i got better. Sorry for my grammar.
If i remove the transition(element); part, the image rotate just fine.
The transition part sets a new value for the style attribute.
Setting a new value for the attribute replaces the old value.
You are removing the style for the background image. (So the one from the stylesheet is applied again instead).
What should i do to get it work?
Don't use setAttribute(..., ...) to modify styles.
Use .style.cssPropertyName = ... instead.
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