即使水平滚动,如何使放置在fabricjs对象顶部的DOM元素保持在那里
Posted
技术标签:
【中文标题】即使水平滚动,如何使放置在fabricjs对象顶部的DOM元素保持在那里【英文标题】:How to make DOM element that is placed on top of fabricjs object stay there even when scrolling horizontally 【发布时间】:2021-06-18 22:30:52 【问题描述】:我有一个包含对象数组的画布,这些对象被定位并添加到画布中。 每个对象都有一个匹配的 DOM 元素来触发工具提示。该元素正好放置在画布对象的顶部。
在桌面上这很好用,因为背景图像总是填满屏幕并且没有滚动条。但在移动设备上,我有一个水平滚动条,因此用户可以在图像上左右滚动(否则它会变小)。
问题是,位于织物对象顶部的 DOM 元素根据对象所在的位置保持在其位置,而没有任何滚动,当我水平滚动时,DOM 元素保持在同一位置。
我在手机上制作了一段视频,显示如下:https://streamable.com/xn1t2i 带圆圈的点是画布外放置在画布对象上的 DOM 元素(没有圆圈的蓝点)。
所以我想到了以下解决方案:将整个脚本放在一个函数中,然后在一个事件上调用该函数,例如:touchmove
但是这非常慢,并且在移动时会显示很多闪烁。所以我尝试了touchend
,但这也很慢,并且在单击工具提示时也会触发该功能。
touchmove
的示例视频:https://streamable.com/708d2s 如您所见,点确实会重新定位,但速度太慢,如果滚动拖得太久,点就会再次错位。
我也尝试过scroll
,但这根本不起作用。
这是我目前的代码:
(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" style="display:none;">
<div class="canvas-container" style="width: 100%; position: relative;">
<canvas id="c" 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?.." 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" >
</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?.." ><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!" 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" >
</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!" ><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!" 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" >
</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!" ><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" 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" >
</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" ><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" 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" >
</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" ><span class="btnstyle purplebtn">Stel zitzak samen</span></a>
</div>
</div>
</div>
</span>
整个页面的代码笔:https://codepen.io/twan2020/pen/VwPZmJx 可能会尝试在您的手机上查看此内容,因为由于某种原因,在桌面上将屏幕调整为移动尺寸时会破坏画布。不过在我的手机上它运行良好。
如何确保 DOM 元素点始终停留在画布对象点上?在保持当前速度的同时?
【问题讨论】:
你在这方面有什么进展吗?通常,当我使用画布并且有很多对象时,我会使用分层技术。我没有笔记本电脑,但大约 12 小时后,我可以想出一个有趣的解决方案。 @danielm2402 不幸的是,我想出的所有解决方案都非常缓慢。例如,我尝试过的事件监听器。此外,当您在移动设备上拖动屏幕并松开手指时,它会稍微拖动直到移动停止,最后一次移动将再次错位。 【参考方案1】:在 .canvas-container
和 #cirkel1 ... #cirkelN
div 周围包裹一个新的相对定位 div
,以便相关的 html 代码区域的结构如下:
<div class="canvas-container-container">
<div class="canvas-container">...</div>
<span id="cirkel1">...</span>
<span id="cirkel2">...</span>
...
</div>
重要的是.canvas-container-container
具有position: relative;
,以便相对于.canvas-container-container
的原点定位其绝对定位的子#cirkel1 ... #cirkelN
。这种变化意味着.canvas-container-container
的原点对应你画布中的(0,0)
点,这解决了你的问题的根本问题。
更新您的 css 代码,使 .canvas-container
的 css 规则成为 canvas-container-container
的规则(确保删除相应的 .canvas-container
css 代码):
.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;
这样,水平滚动应该已经可以工作了。但是,由于您的 #cirkel1 ... #cirkelN
现在相对于 .canvas-container-container
定位,您不再需要在您的 getAbsoluteCoords
中添加 this._offset.left
和 menuheightcanvas
:
fabric.Canvas.prototype.getAbsoluteCoords = function(object)
return
left: object.left, // +this._offset.left not needed here
top: object.top, // +menuheightcanvas not needed here
;
事实上,你甚至根本不需要这个getAbsoluteCoords
,因为 obj.left 和 obj.top 已经相对于画布原点,因此不需要偏移:
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';
【讨论】:
谢谢!效果很好。我会在可能的情况下接受赏金。还有几个小时。 感谢您的编辑,我同时在编辑,所以您的编辑由于冲突而被自动拒绝(如果我没记错的话)。但是我后来添加了你的修复。总是乐于提供帮助。 是的,我看到了,没问题。关于你的回答,我有一个问题。画布的内容现在是相对于容器的,这也意味着工具提示不会显示在画布之外。有一个简单的解决方案吗?示例:i.gyazo.com/f98274f7c82e1408b33c89886e9bc8c7.png 尝试在没有overflow-y: hidden;
的情况下显示.canvas-container-container
。让我知道工具提示是否仍然被切断
还有一个替代方案:增加.canvas-container-container
的高度,使其足以覆盖所有工具提示框。使用相对定位或负边距使下面的内容与画布容器部分重叠。如果需要,使用 css 删除 .canvas-container-container
的滚动条。以上是关于即使水平滚动,如何使放置在fabricjs对象顶部的DOM元素保持在那里的主要内容,如果未能解决你的问题,请参考以下文章