Vue: get image width + height returns zero on component load - vue.js

My component is a modal which displays an <img> with some tags on top.
I get the image dimensions using const size = this.$refs.media.getBoundingClientRect().
However, the height and width is 0, so the tags become placed wrong.
So it seems the image hasn't loaded yet? If I delay getting the height+width with setTimeout, I do get the width and height. But it seems like a not too reliable workaround.
I have "v-cloak" on the modal, but that doesn't work.
I can get the html element with this.$refs.media, but it doesn't mean that the image is loaded, apparently.
So, is there a way to check if the image is loaded? Is there some way to delay the component until the image is loaded?
One more thing: if I add a "width" and "height" in inline style on the img, then those values are available directly. But I don't want to set the size that way because it ruins the responsiveness.
The code doesn't say much more, but it is basically:
<modal>
<vue-draggable-resizable // <-- each tag
v-for="( tag, index ) in tags"
:x="convertX(tag.position.x)"
:y="convertY(tag.position.y)"
(other parameters)
convertX (x) {
const size = this.$refs.media.getBoundingClientRect()
return x * (size.width - 100) // <--- 0 width
async mounted () {
this.renderTags(this.media.tags) // <-- needs img width and height
Updated, solution
As the below answer says, I can use the onload event. I also came across the "#load" event, which I tried and it worked. I can't find it in the documentation though(?) (I searched for #load and v-on:load). Using "onload" directly on the image element didn't work though, so #load seems to be the way.
If I have a data property:
data () {
return {
imgLoaded: false
I can set it to true with a method using #load on the image:
<img
#load="whenImgLoaded"
<template v-if="imgLoaded = true">
<vue-draggable-resizable
:x="convertX(tag.position.x)"
:y="convertY(tag.position.y)"
(other parameters)
methods: {
whenImgLoaded(){
this.imgLoaded = true;
this.renderTags(this.media.tags)
}

You could load the image first via javascript, and after it's loaded run the renderTags method.
E.g., do something like this:
async mounted() {
let image = new Image();
image.onload = function () {
this.renderTags(this.media.tags)
};
image.src = 'https://some-image.png'
}

Related

Vue Leaflet Setting custom TileLayer bounds

I'm trying to display a large number of images using leaflet and I'm having problems making the tileLayers display properly.
I'm extending the TileLayer class and overriding the createTile function like this:
`
let imageLayer = TileLayer.extend({
createTile: function (coords, done) {
const image = window.L.DomUtil.create("img");
image.src = `http://placekitten.com/g/200/300`;
image.crossOrigin = "";
image.onload = function () {
this.onload = function () {};
done(null, image);
};
return image;
}
});
Result
I want to change the position of the images and to only display them in areas I have already defined (for example between offsetting the image at 0,0 to be at 1,1 or using latLng
([8273.300000000001, 10618.02] , [9232.653535353536, 9658.666464646465])).
Ive tried a bunch of different methods and almost always came back to bounds, which don't work and setting the bounds doesn't change anything.
I don't want to use ImageOverlays because the images I'm trying to display are sliced and named with the leaflet naming scheme (0_0_1.png).

Does pdf.js allow rendering of a selected (rectangular) part of a page instead of rendering an entire page to a canvas?

Does pdf.js allow to render a PDF page only partially? More specifically, is it possible to tell pdf.js to render a selected "rectangle of pixels" out of an entire PDF page?
Assuming a resolution of 144 dpi, a typical page (DIN A4) would have approx. 684 (width) by 1190 (height) pixels. I would like to render (for example) a rectangle like [100, 100] (top left coordinate in pixels) and [400, 400] (bottom right coordinate in pixels).
A typical use case could be a scanned document with several handwritten notes that I would like to display and further process individually.
I do understand that a "workaround" could be to save the entire page as jpg (or any other suitable bitmap format) and apply some clipping function. But this would for sure be a less performant approach than selected rendering.
pdfs.js uses a viewport object (presumably containing parameters) for rendering. This object contains
height
width
offsetX
offsetY
rotation
scale
transform
viewBox (by default [0, 0, width / scale, height / scale])
One might think that manipulating the viewBox inside it might lead to the desired outcome, but I have found that changing the viewBox parameters does not do anything at all. The entire page is rendered every time that I apply the render method.
What might I have done wrong? Does pdf.js offer the desired functionality? And if so, how can I get it to work? Thank you very much!
Here is a very simple React component demonstrating my approach (that does not work):
import React, { useRef } from 'react';
import { pdfjs } from 'react-pdf';
pdfjs.GlobalWorkerOptions.workerSrc = 'pdf.worker.js';
function PdfTest() {
// useRef hooks
const myCanvas: React.RefObject<HTMLCanvasElement> = useRef(null);
const test = () => {
const loadDocument = pdfjs.getDocument('...');
loadDocument.promise
.then((pdf) => {
return pdf.getPage(1);
})
.then((page) => {
const viewport = page.getViewport({ scale: 2 });
// Here I modify the viewport object on purpose
viewport.viewBox = [100, 100, 400, 400];
if (myCanvas.current) {
const context = myCanvas.current.getContext('2d');
if (context) {
page.render({ canvasContext: context, viewport: viewport });
myCanvas.current.height = viewport.height;
myCanvas.current.width = viewport.width;
}
}
});
};
// Render function
return (
<div>
<button onClick={test}>Test!</button>
<canvas ref={myCanvas} />
</div>
);
}
export default PdfTest;
My initial thought was also to modify a viewBox of page Viewport. This was not the right guess (I hope that you already figured it out).
What do you need really to do to project only a part of a page to canvas is to prepare correctly the transformation of Viewport.
So it will look more or less like following:
const scale = 2
const viewport = page.getViewport({
scale,
offsetX: -100 * scale,
offsetY: - 100 * scale
})
This will move your your box section to the beginning of the canvas coordinates.
What probably you would like to do next is to make a canvas equal to the selected rectangle size (in your case is 300x300 scaled by your scale) and this solved the issue in my case.

vue-router view caching / not resetting data

Im working on a car sales website and using vue-router.
I have an index page with a list of all cars for sale, and then when clicked they link to the single view of that specific vehicle.
I have a large 'header' image on the single view page and have it inside a container with a fixed height so that when the page loads there is not jumping in page height.
When going to this single view, I do an API call to get the vehicle data and then wish to fade in the heading image.
To do this:
<div class="singleVehicle__mainImage">
<span :style="styles" :class="{'imageLoaded' : mainImageLoaded }" v-if="vehicle"></span>
</div>
export default {
data() {
return {
vehicle: null,
styles: {
backgroundImage: null
},
mainImageLoaded: null
}
},
created() {
this.getVehicle().then(() => {
this.mainImageBackground();
});
},
methods: {
mainImageBackground() {
var source = "IMAGE SOURCE URL";
this.styles.backgroundImage = "url("+source+")";
var $this = this;
var img = new Image();
img.onload = function() {
$this.mainImageLoaded = true;
}
img.src = source;
if (img.complete) img.onload();
}
}
}
By default, the span inside the image wrapper has a 0 opacity and then that is transitioned to 1 when the .imageLoaded class is added.
This works fine, but only the first time each vehicle loaded. The image waits till it loads and then fades in. Everyother time afterwards the image simply pops in when it loads almost like the imageLoaded class is not being reset / the data is not being reset when leaving the view.
When clearing browser cache, it works again but once for each vehicle view.
This is probably due to your v-if="vehicle". The vehicle call is possibly taking longer and so the span is not showing until after the class is added or some timing issue related to that.

Bootstrap affix smooth scroll

I have been working on bootstrap scrollspy (affix) and it worked fine. I added a script for a smooth movement whenever you click sections.
But it doesn't seem to be working properly.
When you click "section3", it's not move section3.
When you double click, it goes wrong section.
fix nav is not fixed and not showing on responsive.
This is an example page, with the following code:
$(document).ready(function() {
// Add scrollspy to <body>. changed to half-right-wrap
$('div.half-right-wrap').scrollspy({
target: ".navbar",
offset: 50
});
// Add smooth scrolling on all links inside the navbar
$("#myNavbar a").on('click', function(event) {
// Make sure this.hash has a value before overriding default behavior
if (this.hash !== "") {
// Prevent default anchor click behavior
event.preventDefault();
// Store hash
var hash = this.hash;
// Using jQuery's animate() method to add smooth page scroll
// The optional number (800) specifies the number of milliseconds it takes to scroll to the specified area
$('div.half-right-content').animate({
scrollTop: $(hash).offset().top
}, 600, function() {
// Add hash (#) to URL when done scrolling (default click behavior)
window.location.hash = hash;
});
} // End if
});
});
** Please have a look at PC size to see "affix nav"
How can we make it properly? I guess I added wrong selector in js. It was "body" and add my own selector.

Use Dojo boxConstrainedMoveable to constrain movable div to window

I have a div, to which I applied Dojo dojo/dnd/Moveable. But, I'd like to prevent the user from dragging the div offscreen. So, I think I need to implement dojo/dnd/move/boxConstrainedMoveable.
I'm starting with this:
var dnd = new Moveable(this.domNode, {
'handle': this.titleNode
});
There's a similar SO question here:
Constrain a moveable object in Dojo. Applying that answer, I get something like this:
var dnd = new move.boxConstrainedMoveable(
'handle': this.titleNode
constraints: {
l: 0,
t: 20,
w: 500,
h: 500
},
within: true
);
But, I just can't understand how the bounding box works. I simply want the div to stay inside the window. I've tried implementing a few things with the window box, the div's margin box. Nothing's worked, and all I've made is a big mess.
I read the docs here:
http://dojotoolkit.org/api/?qs=1.9/dojo/dnd/move.boxConstrainedMoveable
Has anyone done this with Dojo? I'd be very appreciate of an example.
I looked up some old code I have and I did implement this type of movable once. This was written against Dojo 1.7, so things may have changed in 1.9. Fiddle demonstration: https://jsfiddle.net/4ev1daqr/26/
The main difference between your attempted solution and this is that the constraints property in the moveable needs to be a function rather than a static bounding box. When using the boxConstrainedMoveable module, the static bounding box should be assigned to a box property, rather than the constraints property.
This is actually a nice design, IMHO, because it allows the constraints to react to changes in application state, e.g. hiding a sidebar or moving a splitter, but it does make the simple case a bit more difficult to get working.
define(["dojo/_base/declare",
"dojo/dnd/move",
"dojo/dom",
"dojo/_base/window",
"dojo/dom-style",
"dojo/dom-geometry",
],
function (declare, move, dom, win, domStyle, domGeom) {
return declare( "my/dnd/move/BodyConstrainedMoveable", [move.constrainedMoveable], {
markupFactory: function(params, node){
return new this(node, params);
},
constructor: function(node, params) {
// Constrain the node to be within the body
this.constraints = function() {
var n = win.body(),
s = domStyle.getComputedStyle(n),
mb = domGeom.getMarginBox(n, s);
if ( this.node ) {
var menubox = domGeom.getMarginBox(this.node);
mb.w -= menubox.w;
mb.h -= menubox.h;
}
return mb;
};
}
})});