Skip to content

Instantly share code, notes, and snippets.

@TimCodes
Created April 23, 2018 21:12
Show Gist options
  • Select an option

  • Save TimCodes/abe11532ad73f62c72bfc662625b08ff to your computer and use it in GitHub Desktop.

Select an option

Save TimCodes/abe11532ad73f62c72bfc662625b08ff to your computer and use it in GitHub Desktop.
React Horizontal Scrolling Menu
<div id="root"></div>
let linkArr = [
{ label : "Tables", isSelected : true} , { label : "Chairs", isSelected : false} , { label : "Beds", isSelected : false},
{ label : "Ricks", isSelected : false} , { label : "Morty's", isSelected : false},{ label : "Sanchez", isSelected : false},
{ label :"PinkBear", isSelected : false} , { label :"Raven", isSelected : false}, { label : "Penis", isSelected : false}
]
const leftButton = ({onClick}) => (
<button onClick={onClick} className="pn-Advancer pn-Advancer_Left" type="button">
<svg className="pn-Advancer_Icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 551 1024">
<path d="M445.44 38.183L-2.53 512l447.97 473.817 85.857-81.173-409.6-433.23v81.172l409.6-433.23L445.44 38.18z"/>
</svg>
</button>
);
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 6,
linkArr : linkArr,
navBarTravelling: false,
navBarTravelDirection: "",
navBarTravelDistance: 150,
last_known_scroll_position: 0,
ticking: false,
hrNavOverFlow : "right"
};
this.setSelected = this.setSelected.bind(this);
this.determineOverflow = this.determineOverflow.bind(this);
this.handleNavScroll = this.handleNavScroll.bind(this);
this.setNavOverFlow = this.setNavOverFlow.bind(this);
this.handleAdvanceLeftClick = this.handleAdvanceLeftClick.bind(this);
this.handleAdvanceRightClick = this.handleAdvanceRightClick.bind(this);
this.handleNavTransition = this.handleNavTransition.bind(this);
}
componentWillMount(){
document.documentElement.classList.remove("no-js");
document.documentElement.classList.add("js");
}
componentDidMount(){
console.log("--- container monuted ---");
console.log(this.refs.hrzSlideNavContents)
this.setNavOverFlow();
let { hrzSlideNav, hrzSlideNavContents } = this.refs;
hrzSlideNavContents.getDOMNode().addEventListener(
"transitionend",this.handleNavTransition ,false);
}
setNavOverFlow(){
let {hrzSlideNav, hrzSlideNavContents} = this.refs;
this.setState(
{hrNavOverflow : this.determineOverflow(hrzSlideNavContents.getDOMNode(), hrzSlideNav.getDOMNode())}
);
hrzSlideNav.getDOMNode()
.setAttribute( "data-overflowing",
this.determineOverflow(hrzSlideNavContents.getDOMNode(), hrzSlideNav.getDOMNode())
)
}
handleNavScroll(){
console.log("handlenavScroll")
let{last_known_scroll_position, ticking} = this.state;
this.setState({last_known_scroll_position :window.scrollY});
if (!ticking) {
// use animation frame to get off of main thread
window.requestAnimationFrame(function() {
doSomething(last_known_scroll_position);
this.setStae({ticking : false});
});
}
this.setStae({ticking : true});
}
handleAdvanceLeftClick(){
let { navBarTravelDistance, navBarTravelling, navBarTravelDirection } = this.state;
let { hrzSlideNav, hrzSlideNavContents } = this.refs;
// If in the middle of a move return
if (navBarTravelling === true) {
return;
}
// If we have content overflowing both sides or on the left
if (this.determineOverflow(hrzSlideNavContents.getDOMNode(), hrzSlideNav.getDOMNode()) === "left" ||
this.determineOverflow(hrzSlideNavContents.getDOMNode(), hrzSlideNav.getDOMNode()) === "both") {
// Find how far this panel has been scrolled
var availableScrollLeft = hrzSlideNav.getDOMNode().scrollLeft;
// If the space available is less than two lots of our desired distance, just move the whole amount
// otherwise, move by the amount in the settings
if (availableScrollLeft < navBarTravelDistance * 2) {
hrzSlideNavContents.getDOMNode().style.transform = "translateX(" + availableScrollLeft + "px)";
} else {
hrzSlideNavContents.getDOMNode().style.transform = "translateX(" + navBarTravelDistance + "px)";
}
// We do want a transition (this is set in CSS) when moving so remove the class that would prevent that
hrzSlideNavContents.getDOMNode().classList.remove("pn-ProductNav_Contents-no-transition");
// Update our settings
this.setState({
navBarTravelDirection : "left",
navBarTravelling: true
})
}
// Now update the attribute in the DOM
hrzSlideNav.getDOMNode().setAttribute( "data-overflowing", this.determineOverflow(hrzSlideNavContents.getDOMNode(), hrzSlideNav.getDOMNode()) );
}
handleAdvanceRightClick(){
console.log("handle right");
let { navBarTravelDistance, navBarTravelling, navBarTravelDirection } = this.state;
let { hrzSlideNav, hrzSlideNavContents } = this.refs;
// If in the middle of a move return
if (navBarTravelling === true) {
return;
}
// If we have content overflowing both sides or on the right
if (this.determineOverflow(hrzSlideNavContents.getDOMNode(), hrzSlideNav.getDOMNode()) === "right" ||
this.determineOverflow(hrzSlideNavContents.getDOMNode(), hrzSlideNav.getDOMNode()) === "both") {
// Get the right edge of the container and content
var navBarRightEdge = hrzSlideNavContents.getDOMNode().getBoundingClientRect().right;
var navBarScrollerRightEdge = hrzSlideNav.getDOMNode().getBoundingClientRect().right;
// Now we know how much space we have available to scroll
var availableScrollRight = Math.floor(navBarRightEdge - navBarScrollerRightEdge);
// If the space available is less than two lots of our desired distance, just move the whole amount
// otherwise, move by the amount in the settings
if (availableScrollRight < navBarTravelDistance * 2) {
hrzSlideNavContents.getDOMNode().style.transform = "translateX(-" + availableScrollRight + "px)";
} else {
hrzSlideNavContents.getDOMNode().style.transform = "translateX(-" + navBarTravelDistance + "px)";
}
// We do want a transition (this is set in CSS) when moving so remove the class that would prevent that
hrzSlideNavContents.getDOMNode().classList.remove("pn-ProductNav_Contents-no-transition");
// Update our settings
this.setState({
navBarTravelDirection : "right",
navBarTravelling: true
})
}
// Now update the attribute in the DOM
hrzSlideNav.getDOMNode().setAttribute("data-overflowing", this.determineOverflow(hrzSlideNavContents.getDOMNode(), hrzSlideNav.getDOMNode() ));
}
handleNavTransition(){
console.log("on transtion handler");
let { hrzSlideNav, hrzSlideNavContents } = this.refs;
let { navBarTravelDistance, navBarTravelling, navBarTravelDirection } = this.state;
hrzSlideNav = hrzSlideNav.getDOMNode();
hrzSlideNavContents = hrzSlideNavContents.getDOMNode();
// get the value of the transform, apply that to the current scroll position (so get the scroll pos first) and then remove the transform
var styleOfTransform = window.getComputedStyle( hrzSlideNavContents, null);
var tr = styleOfTransform.getPropertyValue("-webkit-transform") || styleOfTransform.getPropertyValue("transform");
// If there is no transition we want to default to 0 and not null
var amount = Math.abs(parseInt(tr.split(",")[4]) || 0);
hrzSlideNavContents.style.transform = "none";
hrzSlideNavContents.classList.add("pn-ProductNav_Contents-no-transition");
// Now lets set the scroll position
if (navBarTravelDirection === "left") {
hrzSlideNav.scrollLeft = hrzSlideNav.scrollLeft - amount;
} else {
hrzSlideNav.scrollLeft = hrzSlideNav.scrollLeft + amount;
}
this.setState({navBarTravelling : false});
}
setSelected(label){
let newLinkArr =this.state.linkArr.map( l => {
console.log(l)
if(l.label == label){
l.isSelected = true;
}else{
l.isSelected = false;
}
return l;
})
this.setState({linkArr: newLinkArr});
}
determineOverflow =(content, container) => {
var containerMetrics = container.getBoundingClientRect();
var containerMetricsRight = Math.floor(containerMetrics.right);
var containerMetricsLeft = Math.floor(containerMetrics.left);
var contentMetrics = content.getBoundingClientRect();
var contentMetricsRight = Math.floor(contentMetrics.right);
var contentMetricsLeft = Math.floor(contentMetrics.left);
if (containerMetricsLeft > contentMetricsLeft && containerMetricsRight < contentMetricsRight) {
return "both";
} else if (contentMetricsLeft < containerMetricsLeft) {
return "left";
} else if (contentMetricsRight > containerMetricsRight) {
return "right";
} else {
return "none";
}
};
render() {
let{linkArr, hrNavOverFlow} = this.state;
let links = linkArr.map(l =>
<a
className = "pn-ProductNav_Link"
aria-selected={l.isSelected}
onClick = {() => this.setSelected(l.label)}
>
{l.label}
</a>
);
return (
<div className="pn-ProductNav_Wrapper">
<nav ref="hrzSlideNav" className="pn-ProductNav" data-overflowing="right">
<div ref="hrzSlideNavContents" onTransitionEnd = {() => this.handleNavTransition } className="pn-ProductNav_Contents">
{links}
</div>
<span classNameName="pn-ProductNav_Indicator"></span>
</nav>
<button onClick={this.handleAdvanceRightClick} className="pn-Advancer pn-Advancer_Right" type="button">
<svg className="pn-Advancer_Icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 551 1024"><path d="M105.56 985.817L553.53 512 105.56 38.183l-85.857 81.173 409.6 433.23v-81.172l-409.6 433.23 85.856 81.174z"/></svg>
</button>
</div>
);
}
}
React.render(
<Counter/>,
document.getElementById("root")
);
<script src="//cdnjs.cloudflare.com/ajax/libs/react/0.13.0/react.min.js"></script>
.pn-ProductNav_Wrapper {
position: relative;
padding: 0 11px;
width: 200px;
}
.pn-ProductNav {
/* Make this scrollable when needed */
overflow-x: auto;
/* We don't want vertical scrolling */
overflow-y: hidden;
/* For WebKit implementations, provide inertia scrolling */
-webkit-overflow-scrolling: touch;
/* We don't want internal inline elements to wrap */
white-space: nowrap;
/* If JS present, let's hide the default scrollbar */
.js & {
/* Make an auto-hiding scroller for the 3 people using a IE */
-ms-overflow-style: -ms-autohiding-scrollbar;
/* Remove the default scrollbar for WebKit implementations */
&::-webkit-scrollbar {
display: none;
}
}
/* positioning context for advancers */
position: relative;
}
.pn-ProductNav_Contents {
float: left;
transition: transform .2s ease-in-out;
}
.pn-ProductNav_Contents-no-transition {
transition: none;
}
.pn-ProductNav_Link {
text-decoration: none;
color: #888;
font-size: 1.2em;
font-family: -apple-system, sans-serif;
display: inline-flex;
align-items: center;
min-height: 44px;
& + & {
margin-left: 11px;
padding-left: 11px;
border-left: 1px solid #eee;
}
&[aria-selected="true"] {
color: #111;
}
}
.pn-Advancer {
/* Reset the button */
appearance: none;
background: transparent;
padding: 0;
border: 0;
&:focus {
outline: 0;
}
&:hover {
cursor: pointer;
}
/* Now style it as needed */
position: absolute;
top: 0;
bottom: 0;
/* Set the buttons invisible by default */
opacity: 0;
transition: opacity .3s;
}
.pn-Advancer_Left {
left: 0;
[data-overflowing="both"] ~ &,
[data-overflowing="left"] ~ & {
opacity: 1;
}
}
.pn-Advancer_Right {
right: 0;
[data-overflowing="both"] ~ &,
[data-overflowing="right"] ~ & {
opacity: 1;
}
}
.pn-Advancer_Icon {
width: 20px;
height: 44px;
fill: #bbb;
}
.pn-ProductNav_Indicator {
position: absolute;
bottom: 0;
left: 0;
height: 4px;
width: 100px;
background-color: #f90;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment