I've got a canvas that has an array of objects, these objects are positioned and added to the canvas. Each object has a matching DOM element that triggers a tooltip. This element is placed exactly on top of the canvas object.
On desktop this works fine since the background image is always filling the screen and there is no scrollbar. But on mobile I have a horizontal scrollbar so users can scroll left and right on the image (else it will become to small).
The problem is, the DOM elements that are positioned on top of the fabric objects stay in their spot according to where the objects are without any scrolling, when I scroll horizontally, the DOM elements keep in their same spot.
I made a video on my phone that shows this: https://streamable.com/xn1t2i Dots with circle are the DOM elements outside the canvas that are placed on the canvas objects (blue dots without circles).
So I thought of the following solution: put the entire script inside a function, and call that function on an event, like: touchmove
this however is very slow and shows a lot of flickering when moving around. So I tried touchend
but this also is pretty slow and also triggers the function when clicking the tooltip.
Example video of touchmove
: https://streamable.com/708d2s As you can see the dots do get repositioned, but way too slow and if the scroll drags on a bit too long, the dots get mis-aligned again.
I've also tried scroll
but this didn't work at all.
This is my code at the moment:
javascript:
(function() {
function reRender(){
var myImg = document.querySelector("#background");
if(window.outerWidth > 767) {
menuheightcanvas = 172.5;
var realWidth = window.innerWidth;
var realHeight = myImg.naturalHeight;
}else{
menuheightcanvas = 99.8;
var realWidth = myImg.naturalWidth - 900;
var realHeight = myImg.naturalHeight;
}
var source = document.getElementById('background').src;
var canvas = new fabric.Canvas('c', {
allowTouchScrolling: true,
selection: false
});
canvas.allowTouchScrolling = true;
canvas.hoverCursor = 'pointer';
canvas.setDimensions({
width: realWidth,
height: realHeight
});
var img = new Image();
// use a load callback to add image to canvas.
img.src = 'https://printzelf.nl/new/assets/images/custom/WOONKAMER.jpg';
fabric.Object.NUM_FRACTION_DIGITS = 10;
fabric.Image.fromURL(source, function(img) {
img.scaleToWidth(canvas.width);
canvas.setBackgroundImage(img);
canvas.requestRenderAll();
});
var scaleToWidth = realWidth / myImg.width;
// alert (scaleToWidth)
const hotspots = [{
top: (140* scaleToWidth),
left: (720* scaleToWidth),
radius: 10,
fill: '#009fe3',
id: 'cirkel1',
hoverCursor: 'pointer',
selectable: false,
imgtop: 71,
imgleft: 236,
imgheight: 335,
imgwidth: 514,
placement: 'right',
tooltipid: 'cirkel1',
imgUrl: 'https://printzelf.nl/new/cms/images/canvas/woonkamer/gordijnen.jpg'
},
{
top: (160* scaleToWidth),
left: (640* scaleToWidth),
radius: 10,
fill: '#009fe3',
id: 'cirkel2',
hoverCursor: 'pointer',
selectable: false,
imgtop: 82,
imgleft: 351,
imgheight: 313,
imgwidth: 337,
placement: 'right',
tooltipid: 'cirkel2',
imgUrl: 'https://printzelf.nl/new/cms/images/canvas/woonkamer/voile.jpg'
},
{
top: (350* scaleToWidth),
left: (120* scaleToWidth),
radius: 10,
fill: '#009fe3',
id: 'cirkel3',
hoverCursor: 'pointer',
selectable: false,
imgtop: 293,
imgleft: 21,
placement: 'right',
imgheight: 81,
imgwidth: 107,
imgUrl: 'https://printzelf.nl/new/cms/images/canvas/woonkamer/fotoblok.jpg'
},
{
top: (275* scaleToWidth),
left: (165* scaleToWidth),
radius: 10,
fill: '#009fe3',
id: 'cirkel4',
hoverCursor: 'pointer',
selectable: false,
imgtop: 283,
imgleft: 127,
placement: 'right',
imgheight: 60,
imgwidth: 57,
imgUrl: 'https://printzelf.nl/new/cms/images/canvas/woonkamer/fotopaneel.jpg'
},
{
top: (430* scaleToWidth),
left: (600* scaleToWidth),
radius: 10,
fill: '#009fe3',
id: 'cirkel5',
hoverCursor: 'pointer',
selectable: false,
imgtop: 365,
imgleft: 227,
placement: 'right',
imgheight: 185,
imgwidth: 396,
imgUrl: 'https://printzelf.nl/new/cms/images/canvas/woonkamer/zitzak.jpg'
}
];
const loadedImages = [];
for (let [idx, props] of hotspots.entries()) {
let c = new fabric.Circle(props);
c.class = 'hotspot';
c.name = 'hotspot-' + idx;
canvas.add(c);
}
fabric.Canvas.prototype.getAbsoluteCoords = function(object) {
return {
left: object.left + this._offset.left,
top: object.top + menuheightcanvas
};
}
var btnWidth = 40,
btnHeight = 40;
function positionBtn(obj, index) {
var absCoords = canvas.getAbsoluteCoords(obj);
var element = document.getElementById('cirkel' + index);
element.style.left = (absCoords.left - btnWidth / 10) + 'px';
element.style.top = (absCoords.top - btnHeight / 10) + 'px';
}
canvas.getObjects().forEach(function(ho, index) {
positionBtn(ho, index + 1);
});
$(".canvastip").each(function(i) {
tippy(this, {
theme: 'blue',
allowHTML: true,
placement: 'right',
animation: 'scale-subtle',
interactive: true,
// popperOptions: {
// strategy: 'fixed',
// modifiers: [
// {
// name: 'flip',
// options: {
// fallbackPlacements: ['bottom', 'bottom'],
// },
// },
// {
// name: 'preventOverflow',
// options: {
// altAxis: true,
// tether: false,
// },
// },
// ],
// },
onShow(instance) {
canvas.getObjects().forEach(function(ho, index) {
if (ho.class && ho.class === 'hotspot') {
if (instance.id == index + 1) {
// check if image was previously loaded
if (loadedImages.indexOf(ho.name) < 0) {
// image is not in the array
// so it needs to be loaded
// prepare the image properties
let imgProps = {
width: ho.imgwidth,
height: ho.imgheight,
left: ho.imgleft* scaleToWidth,
top: ho.imgtop* scaleToWidth,
scaleX: 1* scaleToWidth,
scaleY: 1* scaleToWidth,
selectable: false,
id: 'img-' + ho.name,
hoverCursor: "default",
};
instance.setProps({placement: ho.placement})
var printzelfImg = new Image();
printzelfImg.onload = function(img) {
var printzelf = new fabric.Image(printzelfImg, imgProps);
printzelf.trippyHotspotImage = true;
canvas.add(printzelf);
};
printzelfImg.src = ho.imgUrl;
// update the `loadedImages` array
loadedImages.push(ho.name);
} else {
for (const o of canvas.getObjects()) {
if (o.id && o.id === 'img-' + ho.name) {
o.visible = true;
break;
}
}
canvas.renderAll();
}
}
}
});
},
onHide(instance) {
for (const o of canvas.getObjects()) {
if (o.trippyHotspotImage) {
o.visible = false;
}
}
canvas.renderAll();
},
content: function(reference) {
return reference.querySelector('.tooltipcontentcanvas' + (i + 1));
}
});
});
}
window.addEventListener('scroll', reRender, false);
reRender();
})();
HTML:
<img id="background" src="https://printzelf.nl/new/assets/images/custom/WOONKAMER.jpg" alt="" style="display:none;">
<div class="canvas-container" style="width: 100%; position: relative;">
<canvas id="c" width="100%" height="500" class="lower-canvas" style="position: absolute; width: 100%; height: 500px; left: 0px; top: 0px;"></canvas>
</div>
<span id="cirkel1" class="canvastip" style="border-radius:100%;width: 25px;height:25px;position:absolute;cursor:pointer;">
<div class="tooltipcontentcanvas1 tooltipcontentcanvas darktext" style="position:relative;">
<div class="tooltipwrap">
<a href="product/gordijnen" title="Weet je wat ik graag zou willen zijn?.." alt="Weet je wat ik graag zou willen zijn?.." class="tooltipprodlink">
<span class="tooltipprodlink">v.a. <b>€19,36</b> p/m<sup>2</sup></span>
<img class="tooltipimgprod" src="cms/images/canvas/woonkamer/tooltip/gordijnen.jpg" alt="Gordijnen">
</a>
<div class="tooltipinfo">
<span class="toptitle">Gordijnen</span>
<h2>Weet je wat ik graag zou willen zijn?..</h2>
<span class="sub">
<ul>
<li><img class="vinkje" src="https://printzelf.nl/new/assets/images/custom/vinkje.gif">10 materialen</li>
<li><img class="vinkje" src="https://printzelf.nl/new/assets/images/custom/vinkje.gif">+ handige accessoires</li>
<li><img class="vinkje" src="https://printzelf.nl/new/assets/images/custom/vinkje.gif">Contourfrezen mogelijk</li>
</ul>
</span>
<a href="product/gordijnen" title="Weet je wat ik graag zou willen zijn?.." alt="Weet je wat ik graag zou willen zijn?.."><span class="btnstyle purplebtn">Stel gordijnen samen</span></a>
</div>
</div>
</div>
</span>
<span id="cirkel2" class="canvastip" style="border-radius:100%;width: 25px;height:25px;position:absolute;cursor:pointer;">
<div class="tooltipcontentcanvas2 tooltipcontentcanvas darktext" style="position:relative;">
<div class="tooltipwrap">
<a href="product/vitrage" title="Jouw vitrage wordt een rage!" alt="Jouw vitrage wordt een rage!" class="tooltipprodlink">
<span class="tooltipprodlink">v.a. <b>€19,36</b> p/m<sup>2</sup></span>
<img class="tooltipimgprod" src="cms/images/canvas/woonkamer/tooltip/vitragegordijnen.jpg" alt="Vitragegordijnen">
</a>
<div class="tooltipinfo">
<span class="toptitle">Vitragegordijnen</span>
<h2>Jouw vitrage wordt een rage!</h2>
<span class="sub">
<ul>
<li><img class="vinkje" src="https://printzelf.nl/new/assets/images/custom/vinkje.gif">10 materialen</li>
<li><img class="vinkje" src="https://printzelf.nl/new/assets/images/custom/vinkje.gif">+ handige accessoires</li>
<li><img class="vinkje" src="https://printzelf.nl/new/assets/images/custom/vinkje.gif">Contourfrezen mogelijk</li>
</ul>
</span>
<a href="product/vitrage" title="Jouw vitrage wordt een rage!" alt="Jouw vitrage wordt een rage!"><span class="btnstyle purplebtn">Stel vitragegordijnen samen</span></a>
</div>
</div>
</div>
</span>
<span id="cirkel3" class="canvastip" style="border-radius:100%;width: 25px;height:25px;position:absolute;cursor:pointer;">
<div class="tooltipcontentcanvas3 tooltipcontentcanvas darktext" style="position:relative;">
<div class="tooltipwrap">
<a href="product/fotoblok" title="Dit blok staat als een huis in je huis!" alt="Dit blok staat als een huis in je huis!" class="tooltipprodlink">
<span class="tooltipprodlink">v.a. <b>€19,36</b> p/m<sup>2</sup></span>
<img class="tooltipimgprod" src="cms/images/canvas/woonkamer/tooltip/fotoblok.jpg" alt="Fotoblok">
</a>
<div class="tooltipinfo">
<span class="toptitle">Fotoblok</span>
<h2>Dit blok staat als een huis in je huis!</h2>
<span class="sub">
<ul>
<li><img class="vinkje" src="https://printzelf.nl/new/assets/images/custom/vinkje.gif">10 materialen</li>
<li><img class="vinkje" src="https://printzelf.nl/new/assets/images/custom/vinkje.gif">+ handige accessoires</li>
<li><img class="vinkje" src="https://printzelf.nl/new/assets/images/custom/vinkje.gif">Contourfrezen mogelijk</li>
</ul>
</span>
<a href="product/fotoblok" title="Dit blok staat als een huis in je huis!" alt="Dit blok staat als een huis in je huis!"><span class="btnstyle purplebtn">Stel fotoblok samen</span></a>
</div>
</div>
</div>
</span>
<span id="cirkel4" class="canvastip" style="border-radius:100%;width: 25px;height:25px;position:absolute;cursor:pointer;">
<div class="tooltipcontentcanvas4 tooltipcontentcanvas darktext" style="position:relative;">
<div class="tooltipwrap">
<a href="product/foto-op-paneel" title="Zet jouw lievelingsfoto op een paneel" alt="Zet jouw lievelingsfoto op een paneel" class="tooltipprodlink">
<span class="tooltipprodlink">v.a. <b>€19,36</b> p/m<sup>2</sup></span>
<img class="tooltipimgprod" src="cms/images/canvas/woonkamer/tooltip/fotopaneel.jpg" alt="Fotopaneel">
</a>
<div class="tooltipinfo">
<span class="toptitle">Fotopaneel</span>
<h2>Zet je lievelingsfoto op een paneel</h2>
<span class="sub">
<ul>
<li><img class="vinkje" src="https://printzelf.nl/new/assets/images/custom/vinkje.gif">10 materialen</li>
<li><img class="vinkje" src="https://printzelf.nl/new/assets/images/custom/vinkje.gif">+ handige accessoires</li>
<li><img class="vinkje" src="https://printzelf.nl/new/assets/images/custom/vinkje.gif">Contourfrezen mogelijk</li>
</ul>
</span>
<a href="product/foto-op-paneel" title="Zet jouw lievelingsfoto op een paneel" alt="Zet jouw lievelingsfoto op een paneel"><span class="btnstyle purplebtn">Stel fotopaneel samen</span></a>
</div>
</div>
</div>
</span>
<span id="cirkel5" class="canvastip" style="border-radius:100%;width: 25px;height:25px;position:absolute;cursor:pointer;">
<div class="tooltipcontentcanvas5 tooltipcontentcanvas darktext" style="position:relative;">
<div class="tooltipwrap">
<a href="product/zitzak" title="Geniet rustig van jouw ontwerp" alt="Geniet rustig van jouw ontwerp" class="tooltipprodlink">
<span class="tooltipprodlink">v.a. <b>€19,36</b> p/m<sup>2</sup></span>
<img class="tooltipimgprod" src="cms/images/canvas/woonkamer/tooltip/zitzak.jpg" alt="Zitzak">
</a>
<div class="tooltipinfo">
<span class="toptitle">Zitzakken</span>
<h2>Geniet rustig van jouw ontwerp</h2>
<span class="sub">
<ul>
<li><img class="vinkje" src="https://printzelf.nl/new/assets/images/custom/vinkje.gif">10 materialen</li>
<li><img class="vinkje" src="https://printzelf.nl/new/assets/images/custom/vinkje.gif">+ handige accessoires</li>
<li><img class="vinkje" src="https://printzelf.nl/new/assets/images/custom/vinkje.gif">Contourfrezen mogelijk</li>
</ul>
</span>
<a href="product/zitzak" title="Geniet rustig van jouw ontwerp" alt="Geniet rustig van jouw ontwerp"><span class="btnstyle purplebtn">Stel zitzak samen</span></a>
</div>
</div>
</div>
</span>
Codepen of entire page: https://codepen.io/twan2020/pen/VwPZmJx maybe try to view this on your phone, because for some reason when resizing the screen to mobile size on desktop breaks the canvas. On my mobile it works fine though.
How can I make sure the DOM element dots always stay on the canvas object dots? While keeping the speed it currently it has?
wrap a new relative positioned div
around your .canvas-container
and your #cirkel1 ... #cirkelN
divs so that the relevant html code area is structured like this:
<div class="canvas-container-container">
<div class="canvas-container">...</div>
<span id="cirkel1">...</span>
<span id="cirkel2">...</span>
...
</div>
It is important that .canvas-container-container
has position: relative;
in order to position its' absolute positioned children #cirkel1 ... #cirkelN
relative to the origin of .canvas-container-container
. This change implies that the origin of .canvas-container-container
corresponds to the (0,0)
point in your canvas, which solves the root problem of your issue.
Update your css code so that the css rules of .canvas-container
become the rules of canvas-container-container
(make sure you remove the corresponding .canvas-container
css code):
.canvas-container-container {
position: relative;
height: 500px;
}
@media only screen and (max-width:991px) {
.canvas-container-container{
overflow-x:auto;
overflow-y:hidden;
}
.canvas-container-container,
.canvas-container {
height: 400px;
}
}
With that the horizontal scrolling should be working already. However since your #cirkel1 ... #cirkelN
are now positioned relatively to .canvas-container-container
you won't need to add this._offset.left
and menuheightcanvas
in your getAbsoluteCoords
anymore:
fabric.Canvas.prototype.getAbsoluteCoords = function(object) {
return {
left: object.left, // +this._offset.left not needed here
top: object.top, // +menuheightcanvas not needed here
};
}
In fact you won't even need this getAbsoluteCoords
at all since obj.left and obj.top are already relative to canvas origin and thus don't need the offset:
function positionBtn(obj, index) {
var element = document.getElementById('cirkel' + index);
element.style.left = (obj.left - btnWidth / 10) + 'px';
element.style.top = (obj.top - btnHeight / 10) + 'px';
}