Розгортання контейнерів з посиланнями

Тут показано, як у шапці цієї сторінки працюють елементи, які розгортають додаткові контейнери з посиланнями.

Наприклад, кнопка "фрукти" при кліку лівою кнопкою миші або при активації за допомогою клавіші Enter розгортає контейнер з посиланнями "апельсини", "мандарини", "лимони".

При розгортанні/згортанні в цих кнопок змінюється значення атрибуту aria-expanded ("true" або "false", залежно від стану). Завдяки цьому скрінрідер озвучує відповідні стани при активації елемента: "розгорнуто", "згорнуто".

Завдяки тому, що ці контейнері розташовані в DOM-дереві сторінки одразу за відповідними кнопками, перехід до їхніх посилань здійснюється одразу при першому ж натисканні клавіші Tab.

За потреби, замість <button>...</button> з підлаштованим зовнішнім виглядом можна використати <a role="button">...</a>.

HTML-код цієї шапки:

<div class="flex_row_sta_cen" id="header" style="height: 90px">
    <div style="width: 100%">
        <div class="flex_row_sta_cen">
            <div class="div_header_link" id="link_to_main">
                <a class="header_link" href="../">на головну</a>
            </div>
            
            <div class="div_header_link">
                <button class="button_expander_as_header_link button_expander_expanded_false" aria-expanded="false" target="flying_container_fruits">фрукти</button>
                
                <div class="div_links_flying_container" id="flying_container_fruits" style="display: none">
                    <div><a class="header_link header_link_in_flying_container" href="../">апельсини</a></div>
                    <div><a class="header_link header_link_in_flying_container" href="../">мандарини</a></div>
                    <div><a class="header_link header_link_in_flying_container" href="../">лимони</a></div>
                </div>
            </div>
            
            <div class="div_header_link">
                <button class="button_expander_as_header_link button_expander_expanded_false" aria-expanded="false" target="flying_container_vegetables">овочі</button>
                
                <div class="div_links_flying_container" id="flying_container_vegetables" style="display: none">
                    <div><a class="header_link header_link_in_flying_container" href="../">помідори</a></div>
                    <div><a class="header_link header_link_in_flying_container" href="../">огірки</a></div>
                    <div><a class="header_link header_link_in_flying_container" href="../">капуста</a></div>
                </div>
            </div>
            
            <div class="div_header_link">
                <a class="header_link" href="../">крупи</a>
            </div>
            
            <div class="div_header_link">
                <a class="header_link" href="../">випічка</a>
            </div>
            
            <div class="div_header_link">
                <a class="header_link" href="../">солодощі</a>
            </div>
            
            <div class="div_header_link">
                <a class="header_link" href="../">напої</a>
            </div>
        </div>
    </div>
</div>

Приклад JS-коду, що керує поведінкою контейнерів:

const buttons = document.getElementsByClassName("button_expander_as_header_link");
Array.from(buttons).forEach(button => {
    button.addEventListener("click", () => {
        console.log(".button_expander_as_header_link was clicked");
        FlyingContainers.trigger(button);
    });
});

class FlyingContainers
{
    static writeLogs = true;
    
    static hideAll() {
        this.log("Method FlyingContainers.hideAll was called");
        let buttons = document.querySelectorAll(".button_expander_as_header_link");
        buttons.forEach((button) => {
            let expanded = button.getAttribute("aria-expanded");
            if (expanded == "true") {
                FlyingContainers.fold(button);
            }
        });
    }
    
    static trigger(button) {
        this.log("Method FlyingContainers.trigger was called");
        let expanded = button.getAttribute("aria-expanded");
        this.hideAll();
        if (expanded == "false") {
            this.expand(button);
        }
    }
    
    static expand(button) {
        this.log("Method FlyingContainers.expand was called");
        let target = document.getElementById(button.getAttribute("target"));
        target.style.display = "block";
        button.setAttribute("aria-expanded", "true");
        button.classList.add("button_expander_expanded_true");
        button.classList.remove("button_expander_expanded_false");
    }
    
    static fold(button) {
        this.log("Method FlyingContainers.fold was called");
        let target = document.getElementById(button.getAttribute("target"));
        target.style.display = "none";
        button.setAttribute("aria-expanded", "false");
        button.classList.add("button_expander_expanded_false");
        button.classList.remove("button_expander_expanded_true");
    }
    
    static log(text) {
        if (this.writeLogs) {
            console.log(text);
        }
    }
}

Приклад CSS-коду, що визначає вигляд представлених вище елементів:

.div_header_link {
    margin-right: 32px;
    padding: 0px 0px 2px 16px;
    position: relative;
}

.header_link {
    border-bottom: 2px solid;
    border-bottom-color: rgba(240, 240, 240, 0.8);
    color: #eee;
    font-family: "font_semibold";
    font-size: 1.1em;
    text-decoration: none;
}

.button_expander_as_header_link {
    background: none;
    border: none;
    border-bottom: 2px solid;
    border-bottom-color: rgba(240, 240, 240, 0.8);
    color: #eee;
    cursor: pointer;
    font-family: "font_semibold";
    font-size: 1.1em;
    padding: 0px 0px 2px 0px;
}

.button_expander_expanded_false::after {
    content: " ⯆";
}

.button_expander_expanded_true::after {
    content: " ⯅";
}

.div_links_flying_container {
    background-color: #555;
    padding: 16px 16px 4px 16px;
    position: absolute;
}

.div_links_flying_container div {
    padding: 0px 0px 12px 0px;
}