Search code examples
cssscroll

Scroll snap along continuous element


I'm trying to display a really wide graph and I want the horizontal scrolling to snap to specific regular points along the x-axis. I thought snap-scroll-points-x would do the trick but it appears to be unsupported.

I have tried adding other elements to the container div which I would then make invisible an use them as snap points but the CSS I have to build around it makes the original item mis-sized and the page falls apart.

Is there a way make a single wide element snap to specific points when scrolling? (including adding invisible elements to force the snap points)

<div class="container">
   <svg id="my-graph" width="10000px"></svg>
</div

Solution

  • Yes, using absolutely positioned invisible elements. Style the container with scroll-snap-type: x mandatory; and style the invisible elements with scroll-snap-align: start;.

    In this snippet I have made the snappable elements visible so you can see how they work.

    body {
      font-family: sans-serif;
      font-size: 12px;
    }
    #d1 {
      width: 500px;
      overflow-x: auto;
      border: 3px solid blue;
      scroll-snap-type: x mandatory;
      position: relative;
    }
    #d1 svg {
      width: 1000px;
    }
    .black-stroke, #ticks {
      stroke: black;
    }
    .x-axis-text {
      text-anchor: middle;
    }
    .snappers>div {
      position: absolute;
      top: 0;
      width: 100px;
      height: 100%;
      scroll-snap-align: start;
      box-sizing: border-box;
      opacity: 0.3;
    }
    .snappers>div:nth-child(even) {
      border: 3px solid red;
      background: #ff000044;
    }
    .snappers>div:nth-child(odd) {
      border: 3px solid lime;
      background: #00ff0044;
    }
    .snappers>div:nth-child(1) {left: 0;}
    .snappers>div:nth-child(2) {left: 100px;}
    .snappers>div:nth-child(3) {left: 200px;}
    .snappers>div:nth-child(4) {left: 300px;}
    .snappers>div:nth-child(5) {left: 400px;}
    .snappers>div:nth-child(6) {left: 500px; width: 500px;}
    <div id="d1">
      <svg viewBox="0 0 1000 250" xmlns="http://www.w3.org/2000/svg">
        <path class="black-stroke" d="M 40 200 L 990 200"></path>
        <path class="black-stroke" d="M 50 10 L 50 210"></path>
        <g id="ticks">
          <path d="M 75 200 L 75 210"/>
          <path d="M 100 200 L 100 210"/>
          <path d="M 125 200 L 125 210"/>
          <path d="M 150 200 L 150 210"/>
          <path d="M 175 200 L 175 210"/>
          <path d="M 200 200 L 200 210"/>
          <path d="M 225 200 L 225 210"/>
          <path d="M 250 200 L 250 210"/>
          <path d="M 275 200 L 275 210"/>
          <path d="M 300 200 L 300 210"/>
          <path d="M 325 200 L 325 210"/>
          <path d="M 350 200 L 350 210"/>
          <path d="M 375 200 L 375 210"/>
          <path d="M 400 200 L 400 210"/>
          <path d="M 425 200 L 425 210"/>
          <path d="M 450 200 L 450 210"/>
          <path d="M 475 200 L 475 210"/>
          <path d="M 500 200 L 500 210"/>
          <path d="M 525 200 L 525 210"/>
          <path d="M 550 200 L 550 210"/>
          <path d="M 575 200 L 575 210"/>
          <path d="M 600 200 L 600 210"/>
          <path d="M 625 200 L 625 210"/>
          <path d="M 650 200 L 650 210"/>
          <path d="M 675 200 L 675 210"/>
          <path d="M 700 200 L 700 210"/>
          <path d="M 725 200 L 725 210"/>
          <path d="M 750 200 L 750 210"/>
          <path d="M 775 200 L 775 210"/>
          <path d="M 800 200 L 800 210"/>
          <path d="M 825 200 L 825 210"/>
          <path d="M 850 200 L 850 210"/>
          <path d="M 875 200 L 875 210"/>
          <path d="M 900 200 L 900 210"/>
          <path d="M 925 200 L 925 210"/>
          <path d="M 950 200 L 950 210"/>
          <path d="M 975 200 L 975 210"/>
        </g>
        <path style="fill: none; stroke: rgb(255, 0, 0); stroke-width: 2px;" d="M 58.424 190.21 C 77.703 173.197 92.865 149.837 116.261 139.172 C 124.604 135.369 130.963 154.35 140.033 153.008 C 148.735 151.721 148.653 134.421 157.349 133.094 C 173.552 130.621 189.584 146.887 205.511 143.015 C 230.184 137.016 248.699 116.069 271.692 105.295 C 279.617 101.581 290.719 94.302 297.399 99.957 C 309.928 110.564 308.334 131.46 318.146 144.621 C 323.494 151.794 332.706 155.421 341.086 158.554 C 351.082 162.291 362.006 162.786 372.46 164.928 C 392.02 168.937 411.982 182.666 431.13 177 C 442.847 173.544 439.242 153.768 445.576 143.32 C 455.408 127.104 474.773 92.693 498.22 82.482 C 513.686 75.746 532.403 106.043 535.416 108.514 C 541.662 113.636 553.131 118.222 554.416 117.883 C 571.169 113.466 588.41 111.101 605.541 108.509 C 613.068 107.37 622.009 110.981 628.306 106.704 C 635.014 102.148 633.367 90.216 639.781 85.255 C 643.648 82.264 649.513 86.844 654.391 86.528 C 659.172 86.219 665.204 79.851 668.421 83.402 C 672.754 88.186 674.714 103.453 677.424 109.311 C 684.593 124.809 681.021 140.089 697.187 145.589 C 707.375 149.055 705.19 118.519 710.881 109.385 C 721.427 92.459 723.779 75.58 743.312 71.559 C 753.925 69.374 745.898 93.74 751.491 103.021 C 759.722 116.679 769.47 131.123 783.63 138.456 C 817.466 155.977 804.577 126.939 813.722 118.212 C 817.08 115 823.898 118.539 827.375 115.463 C 846.099 98.903 848.426 82.726 867.685 66.791 C 870.675 64.317 882 89.107 885.632 95.637 C 893.498 115.104 887.178 132.854 904.424 144.83 C 909.671 148.474 917.078 126.533 918.275 122.401 C 922.141 109.06 917.178 108.04 925.752 97.112 C 929.241 92.665 936.859 101.137 940.523 105.441 C 943.011 108.363 946.148 125.667 947.99 129.034 C 954.791 141.465 955.168 143.113 966.08 152.152 C 972.048 157.096 981.428 154.31 989.102 155.389"></path>
        <g class="x-axis-text">
          <text x="75" y="230">1</text>
          <text x="100" y="230">2</text>
          <text x="125" y="230">3</text>
          <text x="150" y="230">4</text>
          <text x="175" y="230">5</text>
          <text x="200" y="230">6</text>
          <text x="225" y="230">7</text>
          <text x="250" y="230">8</text>
          <text x="275" y="230">9</text>
          <text x="300" y="230">10</text>
          <text x="325" y="230">11</text>
          <text x="350" y="230">12</text>
          <text x="375" y="230">13</text>
          <text x="400" y="230">14</text>
          <text x="425" y="230">15</text>
          <text x="450" y="230">16</text>
          <text x="475" y="230">17</text>
          <text x="500" y="230">18</text>
          <text x="525" y="230">19</text>
          <text x="550" y="230">20</text>
          <text x="575" y="230">21</text>
          <text x="600" y="230">22</text>
          <text x="625" y="230">23</text>
          <text x="650" y="230">24</text>
          <text x="675" y="230">25</text>
          <text x="700" y="230">26</text>
          <text x="725" y="230">27</text>
          <text x="750" y="230">28</text>
          <text x="775" y="230">29</text>
          <text x="800" y="230">30</text>
          <text x="825" y="230">31</text>
          <text x="850" y="230">32</text>
          <text x="875" y="230">33</text>
          <text x="900" y="230">34</text>
          <text x="925" y="230">35</text>
          <text x="950" y="230">36</text>
          <text x="975" y="230">37</text>
        </g>
      </svg>
      <div class="snappers">
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
      </div>
    </div>

    It certainly is a pity that #ticks>path {scroll-snap-align: start;} doesn’t work, as that would eliminate the need for the invisible elements.