简体   繁体   中英

overflow-x: scroll and overflow-y: visible alternative

I have a list which is rendering images (horizontally with scroll):

<div id="my-cool-wrapper">
  ...
  // My cool wrapper has more elements (apart from list)

  <ul id="the-list" style="display: flex; flex-direction: row; overflow-x: scroll;">
    <li>
      <img src="https://image.shutterstock.com/image-vector/sample-stamp-grunge-texture-vector-260nw-1389188336.jpg" />
    </li>

    <li>
      <img src="https://image.shutterstock.com/image-vector/sample-stamp-grunge-texture-vector-260nw-1389188336.jpg" />
    </li>

    <li>
      <img src="https://image.shutterstock.com/image-vector/sample-stamp-grunge-texture-vector-260nw-1389188336.jpg" />
    </li>
  </ul>
</div>

I would like to transform: scale(1.5) the images on user interaction (eg click, hover, etc).

The problem:

  • The images do not overflow outside the parent. I want the images to be fully visible when growing (even if it's outside the list's height).

I thought I could achieve this by setting overflow-y: visible to #the-list . However, according to the CSS overflow-x: visible; and overflow-y: hidden; causing scrollbar issue thread this is not possible.

Is there any alternative to achieving what I want?

Update: A JSFiddle is available to demonstrate the issue: http://jsfiddle.net/f7vdebt2/

I would like for the content of the <ul> to be able to scale beyond its parent boundaries.

This is actually a complex and long-standing problem . Solving it with CSS alone is not feasible.

The trick is to pluck the active element out from the static context and force it to be fixed to the viewport when hovered over. I tried to boil this down to a minimal reproducible example but the more I hacked away at it the more quirks I encountered. With fixed image sizes you can accomplish this with a pretty minimal amount of scripting but there are some usability issues and the more of them I fixed, the more complex the code got.

Ultimately, what I ended up doing was publishing a custom element that handles all of this automagically.

Using it is dead simple:

 over-scroll { width: 50%; margin: 3rem auto; } pop-out img { height: 100px; width: auto; }
 <script type="module" src="https://bes.works/bits/bits/pop-out.js"></script> <over-scroll> <pop-out><img src="https://picsum.photos/720/480"></pop-out> <pop-out><img src="https://picsum.photos/480/720"></pop-out> <pop-out><img src="https://picsum.photos/500/500"></pop-out> <pop-out><img src="https://picsum.photos/640/480"></pop-out> </over-scroll>

Or you could import the element classes into a script and create them programatically:

import { OverScrollElement, PopOutElement } from './pop-out.js';

let overScroll = new OverScrollElement();
let popOut = new PopOutElement();
popOut.innerHTML = `<h1> Hello! </h1>`;
overScroll.append(popOut);
document.body.append(overScroll);

There's a test page included in the repository and a live demo on my website with additional examples. This should do the trick for you but let me know if there are any tweaks you would need to suit your specific use case.

try to add these parameters

{
 flex-direction: row;
 flex-wrap: nowrap;
 overflow-x: auto;
}

To be honest, it won't work with only CSS . The best solution in the CSS context will be to move the spacing of the scrollbar down so that it cannot be overlapped.

Another solution would be to use the hovered element only as a trigger to get a copy of the hovered image out of the background and position it with css position so that it lies exactly above the hovered image.

My preferred solution would be to simply remove the navbar and allow scrolling with an arrow on the left and right side. Just like a slider gallery. Of course, this would also be a Javascript solution.

Didn't have to do much, just added bigger padding to the ul and some hover effects. If this isn't what you're looking for please clarify your question more.

 div { overflow: visible; } ul{ position: relative; display: flex; overflow-x: scroll; overflow-y: visible; border: 1px solid blue; border-radius: 4px; margin: 0px; padding: 2rem; } /* decorations. */ li{ list-style: none; padding: 0.2rem; border: 1px solid cyan; border-radius: 2px; margin: 2px; position: relative; transition: .2s ease-out; } li:hover { transition: .2s ease-in; transform: scale(1.5); background-color: #03fce355; cursor: zoom-in; } li::before, li::after { font-weight: bold; font-size: larger; content: "::"; }
 <div> <ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> </ul> </div>

Hope this is what you need

Let me explain what i did here.

When i fill the container with images and add a position:absolute on them to scale, i encounter a problem which is

when i scroll the scrollbar in x direction X-locations[left] of elements are started changing. 5th or 6th elements scale on far right from where they are in the viewport. When i use console.log to see what are their left location. It shows me 2 locations which is very wierd.

So i collect all the images in JS file and apply them

item.style.left = `${rect.left}px` 

when they are scaling and that solve the issue. You can change divs to li items and make them rely on your scenerio.

 const images = document.querySelectorAll('.image'); let rect; for (let i = 0; i < images.length; i++) { const image = images[i]; image.addEventListener("mouseenter", function (event) { rect = this.getBoundingClientRect(); GetBigger(this); },false); } for (let i = 0; i < images.length; i++) { const image = images[i]; image.addEventListener("mouseleave", function (event) { GetSmaller(this); },false); } function GetBigger(item){ item.style.transform = "scale(1.5)"; item.style.position = "absolute"; item.style.left = `${rect.left}px` item.style.zIndex = "999" } function GetSmaller(item){ item.style.transform = "scale(1.0)"; item.style.position = "static"; }
 *, *::before, *::after { box-sizing: border-box; } img, picture { max-width: 100%; display:block; } body{ min-height: 100vh; background-color: bisque; overflow: hidden; }.wrapper{ margin: 7.5rem 10rem; width: inherit; display: flex; flex-direction: row; overflow-x: scroll; }.item img{ min-width: 520px; cursor: pointer; }
 <div class="wrapper"> <div class="item"> <img class="image" src="https://image.shutterstock.com/image-vector/sample-stamp-grunge-texture-vector-260nw-1389188336.jpg" alt="" /> </div> <div class="item"> <img class="image" src="https://image.shutterstock.com/image-vector/sample-stamp-grunge-texture-vector-260nw-1389188336.jpg" alt="" /> </div> <div class="item"> <img class="image" src="https://image.shutterstock.com/image-vector/sample-stamp-grunge-texture-vector-260nw-1389188336.jpg" alt="" /> </div> <div class="item"> <img class="image" src="https://image.shutterstock.com/image-vector/sample-stamp-grunge-texture-vector-260nw-1389188336.jpg" alt="" /> </div> <div class="item"> <img class="image" src="https://image.shutterstock.com/image-vector/sample-stamp-grunge-texture-vector-260nw-1389188336.jpg" alt="" /> </div> <div class="item"> <img class="image" src="https://image.shutterstock.com/image-vector/sample-stamp-grunge-texture-vector-260nw-1389188336.jpg" alt="" /> </div> <div class="item"> <img class="image" src="https://image.shutterstock.com/image-vector/sample-stamp-grunge-texture-vector-260nw-1389188336.jpg" alt="" /> </div> <div class="item"> <img class="image" src="https://image.shutterstock.com/image-vector/sample-stamp-grunge-texture-vector-260nw-1389188336.jpg" alt="" /> </div> <div class="item"> <img class="image" src="https://image.shutterstock.com/image-vector/sample-stamp-grunge-texture-vector-260nw-1389188336.jpg" alt="" /> </div> </div>

Don't know if this work for your requirements.

Basically I

  • added margin-top: 20px; margin-bottom: 20px; margin-top: 20px; margin-bottom: 20px; to the LI to enlarge the UL enough to avoid the y overflow problem
  • nested the UL in a container DIV with the vertical size of images (approximated in the example)
  • added position: relative; top: -18px; position: relative; top: -18px; to the UL to make images aligned with the container DIV (again, approximated in the example)
  • added overflow: hidden; to the UL to remove ugly scrollbars from the page
  • added z-index: 100; to the LI.hover to make the enlarged image fully visible
  • added some JavaScript to scroll horizontally with the mouse wheel

 const list = document.getElementById('list'); const lis = document.querySelectorAll('li'); [list, ...lis].forEach(_ => _.addEventListener('wheel', event => { list.scrollLeft += event.deltaY / 2; event.preventDefault(); }));
 div { overflow: visible; } ul { display: flex; overflow: hidden; position: relative; top: -18px; } li:hover { transform: scale(2); z-index: 100; } /* decorations. */ ul { margin: 0px; width: 100%; } li { background-color: #ffffff; list-style: none; padding: 2px; border: 1px solid cyan; border-radius: 2px; margin: 2px; margin-top: 20px; margin-bottom: 20px; position: relative; } li:before, li:after { font-weight: bold; font-size: larger; content: "::"; }.list-container { height: 30px; }
 <div style="width:550px"> some other content<br /> some other content<br /> <div class="list-container"> <ul id="list"> <li>0</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> <li>0</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> <li>0</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> </ul> </div> some other content<br /> some other content<br /> </div>

Surely the easiest solution is just to add some padding to the parent?

I also added a white background to the list items, and brought the active one to the top with a z-index style in the :hover rule.

 div { overflow: visible; } ul { display: flex; overflow-x: scroll; overflow-y: visible; } li:hover { transform: scale(2); z-index: 99; } /* decorations. */ ul { border: 1px solid blue; border-radius: 4px; margin: 0px; padding: 1em 1.5em; } li { list-style: none; padding: 2px; border: 1px solid cyan; border-radius: 2px; margin: 2px; position: relative; background-color: white; } li:before, li:after { font-weight: bold; font-size: larger; content: "::"; }
 <div> <ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> </ul> </div>

While not as polished as this answer by Besworks , here is a simple solution in JavaScript.

The idea is to hide the active element (set CSS opacity to zero), and overlay a new DIV with the same content and styling.

The overlay DIV is appended to the DOM as a child of the parent DIV. This way, the overlay sees the overflow: visible; property of the parent DIV, instead of the more complicated overflow property of the UL.

The added DIV is placed at the same position as the active LI, and then transformed.

Of course, we also must remember to remove this added DIV when the user's pointer moves away.

 const box = document.getElementById("box"); const outofbox = document.getElementById("outofbox"); const items = Array.from(outofbox.children); let replacement; items.forEach(item => { item.addEventListener("pointerenter", () => replaceLI(item)); item.addEventListener("pointerleave", () => replacement.remove()); }); function replaceLI(li) { const { x, y } = li.getBoundingClientRect(); replacement = document.createElement("div"); replacement.className = "li"; replacement.style.left = x + "px"; replacement.style.top = y + "px"; replacement.innerHTML = li.innerHTML; box.appendChild(replacement); }
 div { overflow: visible; } ul { display: flex; overflow-x: scroll; overflow-y: visible; } li:hover { opacity: 0; } /* decorations. */ ul { border: 1px solid blue; border-radius: 4px; margin: 0px; padding: 0px; } li, div.li { list-style: none; padding: 2px; border: 1px solid cyan; border-radius: 2px; margin: 2px; display: inline-block; } li:before, li:after, div.li:before, div.li:after { font-weight: bold; font-size: larger; content: "::"; } div.li { position: absolute; transform: scale(2); }
 <div id="box"> <ul id="outofbox"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> </ul> </div>

The transform property cannot increase the height of the parent container, because of the scroll property.

All you need is to give the UL a fixed height greater than the max-size of what the enlarged image will look.

Here is the working example.

 div { overflow: visible; } ul { align-items: center; height: 50px; display: flex; /* overflow-x: scroll; */ /* overflow-y: visible; */ overflow: visible auto; } li:hover { transform: scale(2); /* position: relative; */ } /* decorations. */ ul { border: 1px solid blue; border-radius: 4px; margin: 0px; padding: 0px; } li { list-style: none; padding: 2px; border: 1px solid cyan; border-radius: 2px; margin: 2px; position: relative; height: 20px; } li:before, li:after { font-weight: bold; font-size: larger; content: "::"; }
 <div> <ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> </ul> </div>

Hope it helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM