Reputation: 1023
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 (e.g. click, hover, etc).
The problem:
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.
Upvotes: 15
Views: 8077
Reputation: 64
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.
Upvotes: 0
Reputation: 4483
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.
Upvotes: 7
Reputation: 557
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>
Upvotes: 0
Reputation: 89
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>
Upvotes: 2
Reputation: 17556
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.
Upvotes: 1
Reputation: 15797
Don't know if this work for your requirements.
Basically I
margin-top: 20px; margin-bottom: 20px;
to the LI
to enlarge the UL enough to avoid the y overflow problemUL
in a container DIV
with the vertical size of images (approximated in the example)position: relative; top: -18px;
to the UL
to make images aligned with the container DIV
(again, approximated in the example)overflow: hidden;
to the UL
to remove ugly scrollbars from the pagez-index: 100;
to the LI.hover
to make the enlarged image fully visibleconst 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>
Upvotes: 0
Reputation: 1180
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>
Upvotes: 0
Reputation: 14107
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>
Upvotes: 0
Reputation: 11
try to add these parameters
{
flex-direction: row;
flex-wrap: nowrap;
overflow-x: auto;
}
Upvotes: 1