即使水平滚动,如何使放置在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,但这根本不起作用。

这是我目前的代码:

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"  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.leftmenuheightcanvas

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元素保持在那里的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 QScrollArea 使滚动条出现

如何实现顶部带有标题的水平滚动集合视图?

如何水平放置按钮和字段级别

如何摆脱tabhost中的水平滚动条?

垂直放置集合视图单元格而不是水平放置?

即使在 Xamarin Forms 中滚动页面的最右侧或最下方,如何使轴/索引始终可见?