Reputation:
I use a horizontal scroll on a page I built. It's inside a div, named it with a class, and I want to use the arrows keys to scroll. For it to scroll with the keys, I need to click somewhere on it.
Is it possible to use the keys directly on first load of the page without necessarily clicking it?
I need the arrow keys to be functional directly and start the horizontal scroll inside that particular div, if that's possible.
<div id="scroll" tabindex="0">
<ul class=“box” >
<div class=“insidebox >
*/content here using various div to fill the box/*
</div>
</ul>
</div>
<script type="text/javascript">
document.getElementById("scroll").focus();
document.getElementById("box").focus();
document.getElementById("insidebox").focus();
</script>
#scroll {
grid-column:1/2;
grid-row: 2/4;
}
.box {
grid-column:1/2;
grid-row: 2/4;
display: grid;
grid-template-columns: repeat(20,1fr);
overflow-x: scroll;
scroll-snap-type: x proximity;
}
.box::-webkit-scrollbar { width: 0 !important }
.box { overflow: -moz-scrollbars-none; }
.box { -ms-overflow-style: none; }
Upvotes: 0
Views: 9686
Reputation: 291
I had a need for an animated horizontal scrollable navigation bar with left and right arrow keys that only show up if needed (when there are more menu items than can fit in visible portion of the screen).
I found this example -- https://codepen.io/mahish/pen/RajmQw -- but it has a dependency on jQuery for animation and it has flaws in design and execution. I adapted it and came up with an improved version here:
https://codepen.io/KenACollins/pen/ZErBQQo
HTML:
<div id="menu-wrapper" class="menu-wrapper">
<ul id="menu" class="menu">
<li class="item">1</li>
<li class="item">2</li>
<li class="item">3</li>
<li class="item">4</li>
<li class="item">5</li>
<li class="item">6</li>
<li class="item">7</li>
<li class="item">8</li>
</ul>
<div class="arrows">
<button id="leftArrow" class="left-arrow arrow hidden">
<
</button>
<button id="rightArrow" class="right-arrow arrow">
>
</button>
</div>
</div>
<div class="print" id="print-wrapper-size"><b>Wrapper size:</b> <span></span></div>
<div class="print" id="print-menu-size"><b>Total menu size:</b> <span></span></div>
<div class="print" id="print-menu-invisible-size"><b>Invisible menu size:</b> <span></span></div>
<div class="print" id="print-menu-end-offset"><b>Menu end offset:</b> <span></span></div>
<div class="print" id="print-menu-position"><b>Scroll position:</b> <span>0</span></div>
CSS:
body {
margin: 3em;
font-family: Arial, Helvetica, sans-serif;
}
* {
padding: 0;
margin: 0;
}
.menu-wrapper {
position: relative; /* Required for arrow keys to be absolutely positioned child divs inside menu-wrapper parent. */
width: 500px;
height: 100px; /* Intentionally shorter than menu items, to hide horizontal scroll bar. */
margin: 1em auto;
border: 1px solid black;
overflow-x: hidden;
overflow-y: hidden;
display: flex;
align-items: center;
padding: 0 20px; /* Inner space on the left and right sides of wrapper must match flexbox gap space between menu items. */
box-sizing: border-box;
}
ul {
list-style: none; /* Hide unordered list bullet. */
}
.menu {
height: 120px;
/* background: #f3f3f3; */
box-sizing: border-box;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
position: relative; /* Required for animation. */
display: flex;
align-items: center;
gap: 20px; /* Flexbox space between menu items must match the left/right padding of menu wrapper. */
}
.menu .item {
background: #f3f3f3; /* Weird - Visible items inherit this from .menu but not hidden items# 6-8 when they slide in and can be seen. */
width: 75px;
height: 75px;
outline: 1px dotted gray;
box-sizing: border-box;
border-radius: 5px;
display: flex; /* Needed to center number in middle of menu item, solution # 1 of 3. */
align-items: center; /* Needed to vertically center number in middle of menu item, solution # 2 of 3. */
justify-content: center; /* Needed to horizontally center number in middle of menu item, solution # 3 of 3. */
}
.arrow {
position: absolute;
top: 0;
bottom: 0;
/* width: 3em; Excluding width means that arrow div will only be as wide as it needs to be to contain the < or > characters. */
}
.left-arrow {
left: 0;
}
.right-arrow {
right: 0;
}
.hidden {
display: none;
}
.print {
margin: auto;
max-width: 500px;
}
.print span {
display: inline-block;
width: 100px;
}
JavaScript:
/**
This code is based on the following developer's work found here https://codepen.io/mahish/pen/RajmQw with the following improvements:
o Eliminates dependency on jQuery. Is rewritten in vanilla JavaScript with animation frames.
o Scrolls to next hidden menu item and stops, does not jump to the far right which would be bad if a lot of menu items in beginning of list became hidden.
o Has a more realistic design with menu items that are spaced apart and smaller than their container.
o Uses flexbox for positioning.
o Increases count of menu items from 4 to 8 to simulate a real need for scrolling.
o Hides right arrow if all menu items fit in the container eliminating the need for scrolling.
o Simplifies the code by eliminating functions that are only called once.
o Has meaningful variable names and lots of comments to explain how it works.
*/
// DOM elements to track.
const leftArrow = document.getElementById('leftArrow');
const rightArrow = document.getElementById('rightArrow');
const menu = document.getElementById('menu');
// Establish unchanging constants and initialize variables.
const menuWrapperSize = document.getElementById('menu-wrapper').offsetWidth; // Unchanging area of the screen where the menu is always visible.
const menuSize = document.getElementById('menu').offsetWidth; // Includes itemsCount * itemSize but also factors in space between items added by flexbox.
const menuInvisibleSize = Math.max(menuSize - menuWrapperSize, 0); // Fixed portion of scrollable menu that is hidden at all times, or zero if menu fits within container.
const arrowSize = rightArrow.offsetWidth; // Width of each arrow div. In current design, this equates to 12px. Still computes value even if right arrow is hidden, which it is at time this line is executed.
const menuEndOffset = Math.max(menuInvisibleSize - arrowSize, 0); // Fixed portion of scrollable menu that is not obscured by an overlapping arrow key, or zero if no arrow keys are needed.
const itemsCount = document.getElementsByClassName('item').length; // Number of menu items.
const itemSize = document.getElementsByClassName('item')[0].offsetWidth; // offsetWidth includes borders and padding but not margins of a menu item (since all the same, choose first one in array). FYI, clientWidth includes padding but NOT borders and margins.
const itemsSpaceBetween = (menuSize - (itemsCount * itemSize)) / (itemsCount - 1); // Space between menu items is deliberately set to equal menu wrapper padding left/right. In this design it is 20 pixels.
const distanceInPixels = itemSize + itemsSpaceBetween; // Distance to scroll per arrow button click equals width of a menu item plus the space to its right or left. In this design, it is 75 + 20 = 95.
const durationInMilliseconds = 500;
let starttime = null;
// Iniitially, on page load menu items are left aligned and left arrow is hidden. Let's hide right arrow also if there is no need for it (as when all menu items fit within visible container).
if (menuInvisibleSize === 0) {
rightArrow.classList.add("hidden");
}
// Get current left position of menu in pixels.
const getMenuPosition = () => {
return parseFloat(menu.style.left) || 0; // First time, left property is not set so initialize to 0.
};
// Get current distance (in pixels) that we have scrolled.
const getScrolledDistance = () => {
return -1 * getMenuPosition(); // Negate value because this is the only way it will work.
};
// After an arrow key is clicked and menu is animating, check to see where we are and determine which arrow key(s) to show, always resulting in at least one arrow key visible. Also, update data at bottom.
// Notes: o This function is only applicable when all menu items cannot be seen in container at one time and an arrow key is clicked to animate menu.
// o If all menu items fit in visible container, UI will be initially rendered without any arrow keys and this function will never be called.
const checkPosition = () => {
// Calculate where we are right now.
const menuPosition = getScrolledDistance();
// Determine which arrow key(s) to display based on position.
if (menuPosition <= arrowSize) { // SHOW RIGHT ARROW if we are scrolling from far left.
leftArrow.classList.add("hidden"); // FYI, this will NOT create duplicate hidden class if leftArrow already contains it.
rightArrow.classList.remove("hidden");
} else if (menuPosition < menuEndOffset) { // SHOW BOTH ARROWS when in the middle of the menu.
leftArrow.classList.remove("hidden");
rightArrow.classList.remove("hidden");
} else if (menuPosition >= menuEndOffset) { // SHOW LEFT ARROW if we are scrolling as far right as we can go.
leftArrow.classList.remove("hidden");
rightArrow.classList.add("hidden");
}
// Print changing scroll position under the menu for informational purposes.
document.querySelector("#print-menu-position span").textContent = menuPosition + 'px';
};
const animateMenu = (timestamp, startingPoint, distance) => {
const runtime = timestamp - starttime;
let progress = runtime / durationInMilliseconds;
progress = Math.min(progress, 1);
let newValue = (startingPoint + (distance * progress)).toFixed(2) + 'px';
menu.style.left = newValue;
if (runtime < durationInMilliseconds) { // If we still have time remaining...
requestAnimationFrame(function(timestamp) { // Request another animation frame and recursively call THIS function.
animateMenu(timestamp, startingPoint, distance);
})
}
checkPosition();
};
const animationFramesSetup = (timestamp, travelDistanceInPixels) => {
timestamp = timestamp || new Date().getTime(); // if browser doesn't support requestAnimationFrame, generate our own timestamp using Date.
starttime = timestamp;
const startingPoint = getMenuPosition(); // This cannot be defined up top in constants. Need to read current value only during initial setup of arrow button click.
animateMenu(timestamp, startingPoint, travelDistanceInPixels);
};
rightArrow.addEventListener('click', () => requestAnimationFrame(
timestamp => animationFramesSetup(timestamp, -1 * distanceInPixels)
));
leftArrow.addEventListener('click', () => requestAnimationFrame(
timestamp => animationFramesSetup(timestamp, distanceInPixels)
));
// Print unchanging values under the menu for informational purposes.
document.querySelector("#print-wrapper-size span").textContent = menuWrapperSize + 'px';
document.querySelector("#print-menu-size span").textContent = menuSize + 'px';
document.querySelector("#print-menu-invisible-size span").textContent = menuInvisibleSize + 'px';
document.querySelector("#print-menu-end-offset span").textContent = menuEndOffset + 'px';
Upvotes: 2
Reputation: 1364
OK - here is an example of what I mean. If we set the div to have tabindex=0
and add a script to the end of the tbody
element that sets the focus to the div, the left/right cursors keys work on the div scrollbar.
#scrolldiv {height:100px; width:1000px; overflow-x:scroll; white-space: nowrap;}
<div id="scrolldiv" tabindex=0>
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
</div>
<div>
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
</div>
<script type="text/javascript">
document.getElementById("scrolldiv").focus();
</script>
To use a class, here is my entire test page:
<!DOCTYPE html>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<html>
<head>
<title>Scrolling div on first load</title>
<script type="text/javascript">
// no script needed here for this functionality
</script>
<style type="text/css">
.scrolldiv {height:100px; width:1000px; overflow-x:scroll; white-space: nowrap;}
.firstdiv {}
</style>
</head>
<body>
<div class="scrolldiv firstdiv" tabindex=0>
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
</div>
<div class="scrolldiv">
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
</div>
<script type="text/javascript">
// Set the focus to the first (and ONLY) div that uses the 'firstdiv' class
// This ensures that we only target one div as there may be many using 'scrolldiv' and these could appear in any order on the page
document.getElementsByClassName("firstdiv")[0].focus();
</script>
</body>
</html>
Upvotes: 1