Home > front end >  Make div with `position:fixed` scrollable if it exceeds the page's height
Make div with `position:fixed` scrollable if it exceeds the page's height

Time:11-23

I know, there's Fixed Div Overflow is Off Screen, I have a similar but still different problem: my DIV isn't always 100% of the page's height, so bottom:0px won't work for me.
I have a DIV fixed to the top right corner of the page which has some contents, one of them is another div with a variable amount of content. Sometimes the content gets too much and both DIVs expand to contain it - it goes over the edge of the page and doesn't scroll. Here's my code (there's only JS code because the JavaScript generates the HTML and CSS as a widget):

// The plain text in this function (except error messages) is in German because it's for my German website. 
function nachrichtenwidgetAnzeigen() {
    const nw = document.createElement('div');
    nw.id = 'nachrichtenwidget';
    nw.hidden = true;
    nw.style = 'position:fixed;top:0px;right:0px;max-height:calc(100%-3px);max-width:33%;border-left:1px solid blue;border-bottom:1px solid blue;border-bottom-left-radius:6px;background-color:rgba(0,255,0,0.3);color:rgba(0,0,0,0);/*pointer-events:none;opacity:80%;*/';
    nw.nachrichten = document.createElement('div');
    nw.nachrichten.id = 'nachrichten';
    nw.nachrichten.style = 'color:white;background-color:rgba(0,0,0,0);overflow-x: clip;overflow-y:auto;';
    nw.nachrichten.addMsg = nw.addMsg = function(msg, iconURL=undefined, msgTimeout=10_000, options={color:'white',bgColor:'blue',onclickaction:''}) { // Einfach msgTimeout=-1 setzen, um eine permanente Nachricht zu erstellen.
        const msgElmnt = document.createElement('div');
        try {
            if (typeof options.onclickaction === 'function') {
                msgElmnt.addEventListener('click', (e) => {
                    msgElmnt.close();
                    options.onclickaction();
                });
            } else if (typeof options.onclickaction === 'string') {
                msgElmnt.addEventListener('click', (e) => {
                    msgElmnt.close();
                    Function(options.onclickaction)();
                });
            } else {
                throw new TypeError(`options.onclickaction isn\'t of type "function" or "string" (is of type "${typeof options.onclickaction}")!`);
            }
        } catch (e) {
            console.warn('Couldn\'t assign onclickaction to message, ignoring that.');
            console.warn(e);
        }
        // msgElmnt.style = 'pointer-events:auto;';
        if (iconURL!==undefined) {
            msgElmnt.msgImg = document.createElement('img');
            msgElmnt.msgImg.style = 'max-width:33px;max-height:33px;padding-right:3px;';
            msgElmnt.msgImg.src = iconURL;
            msgElmnt.appendChild(msgElmnt.msgImg);
        } else {
            msgElmnt.msgImg = undefined;
        }
        msgElmnt.innerHTML  = msg;
        msgElmnt.id = `msg-${msg.replace(/[^a-z0-9]/gi, '-').toLowerCase()}`;
        msgElmnt.style = 'background-color:blue;color:white;padding:3px;margin:3px;border:0px solid blue;border-radius:3px;display:flex;flex-direction:row;';
        if (msgTimeout >= 0) {
            msgElmnt.style.flexWrap = 'wrap'; // Nur wenn eine Nachrichtenanzeigedauer existiert: flex-wrap auf 'wrap' stellen.
        }
        msgElmnt.timeout = document.createElement('div');
        msgElmnt.timeout.style = 'width:100%;height:3px;border:0px solid blue;border-radius:3px;color:rgba(0,0,0,0);background-color:grey;flex:0 0 100%;';
        msgElmnt.timeout.bar = document.createElement('div');
        msgElmnt.timeout.bar.style = 'width:100%;height:3px;border:0px solid blue;border-radius:3px;color:rgba(0,0,0,0);background-color:lightblue;';
        async function animTimeout(e) {
            msgElmnt.timeout.bar.animate([
                { width: '100%' },
                { width: '0%' }
            ], msgTimeout);
            msgElmnt.timeoutAnimation = msgElmnt.timeout.bar.getAnimations()[0];
            msgElmnt.timeoutAnimation.addEventListener('finish', (e) => {
                msgElmnt.remove();
                nw.countElmnt.innerText = nw.nachrichten.getElementsByTagName('div').length/3;
            }); // Nachricht nach Ablauf der Animation entfernen
        }
        msgElmnt.addEventListener('mouseover', (e) => {
            if (msgElmnt.timeoutAnimation) {
                msgElmnt.timeoutAnimation.pause();
            }
            msgElmnt.style.border = '1px solid white';
            msgElmnt.style.padding = '2px';
        });
        msgElmnt.addEventListener('mouseout', (e) => {
            if (msgElmnt.timeoutAnimation) {
                msgElmnt.timeoutAnimation.play();
            }
            msgElmnt.style.border = '0px solid blue';
            msgElmnt.style.padding = '3px';
        });
        if (msgTimeout>=0) { msgElmnt.timeout.bar.addEventListener('click', animTimeout); } else { msgElmnt.timeout.hidden = true; }
        msgElmnt.close = function() { /* Unscharf und durchsichtig animieren */msgElmnt.remove(); nw.countElmnt.innerText = nw.nachrichten.getElementsByTagName('div').length/3; };
        msgElmnt.timeout.appendChild(msgElmnt.timeout.bar);
        msgElmnt.timeout.bar.click();
        msgElmnt.timeout.bar.removeEventListener('click', animTimeout);
        msgElmnt.appendChild(msgElmnt.timeout);
        nw.nachrichten.appendChild(msgElmnt);
        nw.countElmnt.innerText = nw.nachrichten.getElementsByTagName('div').length/3; // Durch drei, weil jede Nachricht ein DIV-Element mit zwei DIV-Elementen ineinander darin ist.
        return msgElmnt;
    }
    const nwClose = document.createElement('button');
    const nwOpen = document.createElement('button');
    nwClose.id = 'nwClose';
    nwClose.hidden = true;
    nwClose.innerText = '\xa0X\xa0'; // \xa0 entspricht der HTML-Entität  
    nwClose.title = 'Nachrichtenwidget ausblenden';
    nwClose.style = 'background-color:red;border-radius:10px;';
    nwClose.addEventListener('click', (e) => {
        nwClose.hidden = true;
        nw.hidden = true;
        nwOpen.hidden = false;
    });
    nwOpen.id = 'nwOpen';
    nwOpen.hidden = false;
    nwOpen.title = 'Nachrichtenwidget einblenden';
    nwOpen.style = 'position:fixed;top:0px;right:33px;background-color:green;';
    nwOpen.addEventListener('click', (e) => {
        nwOpen.hidden = true;
        nw.hidden = false;
        nwClose.hidden = false;
    });
    const nwTitle = document.createElement('span');
    nwTitle.style = 'color:white;background-color:rgba(0,0,0,0);padding-left:3px;user-select:none;';
    nwTitle.innerText = 'Seitennachrichten';
    nw.countElmnt = document.createElement('span');
    nw.countElmnt.style = 'color:white;background-color:rgba(0,0,0,0);padding:3px;user-select:none;';
    document.body.appendChild(nw);
    nwOpen.appendChild(nw.countElmnt);
    document.body.appendChild(nwOpen);
    nw.appendChild(nwClose);
    nw.appendChild(nwTitle);
    nw.appendChild(nw.nachrichten);
    nwOpen.click();
    return nw;
}

// Spawn the widget: 
notifWidg = nachrichtenwidgetAnzeigen();
notifWidg.addMsg('This is a notification widget. You can close any notification by clicking on it.', 'https://www.lampe2020.de/favicon.ico', -1);

// Just to spawn some messages to show what happens: 
setInterval(() => {
  notifWidg.addMsg('Test notification', undefined, 10_000)
}, 1_000);
/*
The main div:
div#nachrichtenwidget {
    position:fixed;
    top:0px;
    right:0px;
    max-width:33%;
    max-height: calc(100%-3px);
    ...
}

The div with the variable content:
div#nachrichten {
    overflow-x:clip;
    overflow-y:auto;
    ...
*/

image of what I get with my old code vs. what I want (photoshopped because it doesn't work in its current state)

CodePudding user response:

You can use the VH unit (1% of the viewport's height.) If you set height: 100vh to your right window, you can set overflow: auto, which will add the scroll bar when your content exceeds the viewport height.

Of course, you can continue using calc() with VH units to adjust the position/size of your overlapping container. You can also set max-height instead of height.

Another option is to have one "overlap" container, which has a height of 100vh and is fixed, but also has pointer-events: none. Then, it's children (.overlap > *) will have pointer-events: auto. That way, users can click behind the overlap when there is no content inside. Still, I would suggest using just max-height.

.sticky {
    width: 200px;
    height: 100vh;
    position: fixed;
    top: 0;
    right: 0;
    overflow: auto;
}

.sticky div {
    background: red;
    margin-bottom: 5px;
    height: 100px;
}
<p>Scroll the right boxes.</p>

<div >
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
</div>

Of course, you can have your inner div being the scrollable element

  •  Tags:  
  • css
  • Related