Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                mixin samuelipsum
	span You think water moves fast? You should see ice. It moves like it has a mind. Like it knows it killed the world once and got a taste for murder. After the avalanche, it took us a week to climb out. Now, I don't know exactly when we turned on each other, but I know that seven of us survived the slide... and only five made it out. Now we took an oath, that I'm breaking now. We said we'd say it was the snow that killed the other two, but it wasn't. Nature is lethal but it doesn't hold a candle to man.

- var panels = [0,1,2,3,4,5]

mixin panel(num)
	//- .panel-wrapper(stickypanel ng-class="{'active': active}")
	.panel-wrapper(stickypanel ng-class="{'active': current == #{num}}")
		.panel.fr-w
			.panel__left.sticky
				.panel__left__inner
					h1 Panel #{num + 1}
			.panel__right.fOne
				+samuelipsum

mixin panelTab(num)
	.panel-tabs__tab.f-cc
		.panel-tabs__tab__inner Tab #{num}
		
header.fr-ac 
	| Angular Sticky Panels Tabs
	sup *WIP

main(ng-app="app")

	.panels-wrapper.fr-w(stickypanels stickies="sticky" offtop="190" offbottom="0" tabs="panel-tabs" ng-transclude)
	
		.panel-tabs
			//-each item in panels
				+panelTab(item)
				
		.panels.fOne
			each item in panels
				+panel(item)
footer.fr-ac
	| The end.

              
            
!

CSS

              
                .fOne {flex: 1;}
body {
	padding-bottom: 75vh; background: #222331; color: #000;
}
header {
	position: fixed; top:0;left:5%;right: 5%;
	height: 90px;
	z-index: 1;
	font-size: 48px;
	padding: 0 1em;
	background: #212330;
	color: #fff;
	border-bottom: 1px solid #4E4E56;
	sup {font-size: 0.4em; transform: translateY(-0.5em);margin-left: 10px;color: #FF6561;font-weight: 400;}
}
footer {
	z-index: 1;
	font-size: 48px;
	padding: 0 1em;
	background: #212330;
	color: #fff;
	width: 95%;
	margin: 50px auto;
	line-height: 2em;
	/* border-top: 1px solid #4E4E56; */
}
main {
	padding-top: 150px;
}
.panels-wrapper {
	width: 100%;
}

.panel {
	min-height: 45vh;
	width: 100%;
	position: relative;
	&-wrapper {
		padding: 0 25px;		
	}
	&-tabs {
		width: 150px;
		position: relative;
		border-right: 1px solid #3d3d4a;
		&__tab {
			text-align:center;
			width: 100%;
			position: fixed;
			width: 150px;
			padding: 10px 30px 10px 20px;
			line-height: 50px;
			color: #fff;
			border-right: 2px solid transparent;
			&.active {
				color: #1FBAD6;
				border-right-color: currentColor;
			}
			&__inner {
				width: 100%;
				text-align: right;
				cursor: pointer;
				font-size: 14px;
				font-weight: 500;
				text-transform: uppercase;
				letter-spacing: 0.08em;
			}
		}
	}
	&__left {
		width: 30%;
		position: relative;
		&__inner {position: absolute;top:0;width: 100%;background: #ddd;padding: 25px 0;}
		h1 {line-height: 50px; text-align:center;}
	}
	&__right {padding: 0 35px 25px;font-size: 20px;line-height:1.45em;margin-left: 20px;border-left: 1px solid #3d3d4a;color: #f0f0f0;}
}
.panel-wrapper {
	&:nth-of-type(2) .panel__left__inner {background: #85D6C0}
	&:nth-of-type(3) .panel__left__inner {background: #FBF094}
	&:nth-of-type(4) .panel__left__inner {background: #FF6561}
	&:nth-of-type(5) .panel__left__inner {background: #733AFE}
	&:nth-of-type(6) .panel__left__inner {background: lighten(#0E2D6E, 20%)}
}
              
            
!

JS

              
                win = typeof window != 'undefined' and window
doc = typeof document != 'undefined' and document
docElem = doc and doc.documentElement
AE = (el) -> return angular.element(el)

app = angular.module('app',[])

app.directive 'stickypanels', ($$, $timeout) ->
	
	panelsCtrl = ($scope, $element) ->
		
		panels = []
		# $scope.panelTabs = []
		panelScopes = []
		$scope.totalPanels = 0
		$scope.current = 0
		
		self = this
		@scrollY = 0
		@initialized = false
		@unfixTabs = false
		
		offtop = parseInt($scope.offtop) or 0
		offbottom = parseInt($scope.offbottom) or 0
		offsets = offtop + offbottom
		
		matchMedia = window.matchMedia
		mediaQuery = $scope.mediaQuery or false
		
		panelsProps = $element[0].getBoundingClientRect()
	
		getScrollY = -> self.scrollY = window.pageYOffset or doc.scrollTop or 0; return
		
		getPanelTabStops = (panel, reset) ->
			if !reset or !panel.tabStops
				panel.tabStop = self.tabProps.box.height * ($scope.totalPanels - panel.number)
			panel.tabStop
			
		getPanelProps = (panel) ->
			props =
				panel: (panel.panel).getBoundingClientRect()
				sticky: height: parseInt($$.getStyles(panel.sticky,'height').replace(/px;?/, ''))
				parent: 
					box: (panel.parent).getBoundingClientRect()
					marginTop: parseInt($$.getStyles(panel.parent,'margin-top').replace(/px;?/, ''))
			props
			
		getTabs = -> 
			if !$scope.panelTabs
				$scope.panelTabs = $element[0].querySelector('.' + $scope.tabs)
			$scope.panelTabs

		getTabProps = (tab, reset) -> 
			if reset or !self.tabProps
				if !tab then console.log('no tab props / tab provided'); return
				self.tabProps = 
					box: tab.getBoundingClientRect()
					marginTop: parseInt($$.getStyles(tab,'margin-top').replace(/px;?/, ''))
					paddingTop: parseInt($$.getStyles(tab,'padding-top').replace(/px;?/, ''))
			self.tabProps
		
		createTab = (index, title) ->
			AE($scope.panelTabs).append('<div class="panel-tabs__tab"><a class="panel-tabs__tab__inner">' + title + '</a></div>')
			tab = ($scope.panelTabs).children[(index)]
			tabProps = getTabProps(tab)
			tab.style['top'] = (tabProps.box.height * index) + (offtop + tabProps.paddingTop + tabProps.marginTop) + 'px'
			return
		
		setPanel = (panel) ->
			props = getPanelProps(panel)
			panel.panelTop = props.panel.top
			panel.panelHeight = props.panel.height
			panel.stickTop = props.panel.top - (offtop + props.parent.marginTop)
			panel.stickBottom = props.panel.bottom - (props.sticky.height + offtop + offbottom)
			panel.fixTop = offtop + props.parent.marginTop
			panel.fixBottom = props.panel.height - (props.sticky.height + offbottom)
			panel.fixWidth = props.parent.box.width
			panel.sticky.style.width = panel.fixWidth + 'px'
			self.checkStick(panel)
			return
		
		@stickPanel = (panel) ->
			stickyStyle = panel.sticky.style
			
			if panel.isSticking
				
				if stickyStyle['position'] != 'fixed'
					panel.panel.classList.add('is-stuck')
					stickyStyle['position'] = 'fixed'
					stickyStyle['top'] = panel.fixTop + 'px'
				
				if $scope.current != panel.number
					$scope.current = panel.number
					$scope.$apply()
			else
				if stickyStyle['position'] != 'absolute'
					stickyStyle['position'] = 'absolute'
					
				if panel.wasSticking
					if stickyStyle['top'] != panel.fixBottom + 'px'
						panel.panel.classList.remove('is-stuck')
						stickyStyle['top'] = panel.fixBottom + 'px'
				else
					if stickyStyle['top'] != '0px'
						stickyStyle['top'] = '0px'
			return
		
		@checkStick = (panel) ->
			if mediaQuery and !(matchMedia('(' + mediaQuery + ')').matches or matchMedia(mediaQuery).matches) then return
			if self.scrollY < panel.stickTop
				if panel.isSticking
					panel.wasSticking = false
					panel.isSticking = false
			else 
				if self.scrollY < panel.stickBottom
					if !panel.isSticking
						panel.wasSticking = false
						panel.isSticking = true
				else
					if !panel.wasSticking
						panel.isSticking = false
						panel.wasSticking = true
			self.stickPanel(panel)
			return
		
		@stickTabs = (reset) ->
			if self.unfixTabs
				_.forEach panels, (panel) ->
					tabStyle = panel.tab.style
					tabStop = getPanelTabStops(panel, reset)
					if tabStyle['position'] != 'absolute'
						tabStyle['position'] = 'absolute'
						tabStyle['top'] = 'auto'
						tabStyle['bottom'] = tabStop + 'px'
			else
				_.forEach panels, (panel) ->
					tabStyle = panel.tab.style
					if tabStyle['position'] != 'fixed'
						tabStyle['position'] = 'fixed'
						tabStyle['top'] = panel.tabStart + 'px'
						tabStyle['bottom'] = 'auto'
			return
		
		@checkTabs = (reset = false) ->
			if self.scrollY > self.tabStops
				if !self.unfixTabs then self.unfixTabs = true
			else 
				if self.unfixTabs then self.unfixTabs = false
			self.stickTabs(reset)

		@setTabStops = ->
			lastPanel = panels[($scope.totalPanels - 1)]
			if lastPanel
				tabsHeight = self.tabProps.box.height * $scope.totalPanels
				if lastPanel.panelHeight > tabsHeight
					self.tabStops = (lastPanel.panelTop - offsets)
				else 
					self.tabStops = (panelsProps.bottom + tabsHeight - offsets)

		@setPanels = (index) ->
			if index then setPanel(panels[index]); return
			_.forEach panels, (panel) -> setPanel(panel); return

		@register = (elem, panelScope) ->
			stickyParent = elem[0].querySelector('.' + $scope.stickies)
			panel =
				panel: elem[0]
				parent: stickyParent
				sticky: stickyParent.firstElementChild
				title: 'Panel ' + ($scope.totalPanels + 1)
				scope: panelScope
				isSticking: false
				wasSticking: false
				
			panelScopes.push(panelScope)
			panels.push(panel)
			self.setPanels($scope.totalPanels)
			
			if $scope.tabs
				getTabs()
				createTab($scope.totalPanels, panel.title)
				panel.tab = $scope.panelTabs.children[$scope.totalPanels]
				panel.tabStart = (self.tabProps.box.height * $scope.totalPanels + offtop)
				
			$scope.totalPanels++
			return
		
		@unregister = (panelScope) ->
			index = panelScopes.indexOf(panelScope)
			if index >= 0
				panels.splice index, 1
				panelScopes.splice index, 1
			return
		
		updateStates = ->
			_.forEach panels, (panel) -> self.checkStick(panel); return
			if $scope.tabs then self.checkTabs()
			return
		onScroll = ->
			getScrollY()
			updateStates()
			return
		
		onResize = -> 
			getScrollY()
			self.setPanels()
			return
		
		bouncedResize = _.debounce(onResize, 1000)
		
		addListeners = ->
			window.addEventListener 'scroll', onScroll
			# window.addEventListener 'resize', onResize
			window.addEventListener 'resize', bouncedResize
			
		removeListeners = ->
			window.removeEventListener 'scroll', onScroll
			window.removeEventListener 'resize', bouncedResize
			
		onDestroy = -> removeListeners(); return
	
		init = -> 
			getScrollY()
			self.setPanels()
			self.initialized = true
			addListeners()
			$scope.$on('$destroy', onDestroy)
			return
		init()
		
		setActiveTab = ->
			tabs = ($scope.panelTabs).children
			_.forEach tabs, (tab) -> tab.classList.remove('active'); return
			tabs[$scope.current].classList.add('active')
			return
		
		watchTotalPanels = _.debounce(self.setTabStops, 1000)
		
		watchCurrentTab = ->
			if $scope.tabs then setActiveTab($scope.current)
			return
		
		$scope.$watch 'totalPanels', watchTotalPanels, true
		$scope.$watch 'current', watchCurrentTab, true
	
		return

	{
		restrict: 'A'
		controller: panelsCtrl
		scope:
			stickies: '@'
			offtop: '@'
			offbottom: '@'
			mediaQuery: '@'
			tabs: '@'
		transclude: true
	}

app.directive 'stickypanel', ->
	linkFn = (scope, elem, attrs, panelsCtrl) ->
		panelsCtrl.register(elem, scope)
		scope.active = false
		scope.resetProps = -> panelsCtrl.setPanels(); return
		scope.$on '$destroy', -> panelsCtrl.unregister(scope); return
		return
	{
		require: '^stickypanels'
		restrict: 'A'
		link: linkFn
		scope: title: '@'
	}

helperFactory = ->
	getStyles: (elem, prop) ->
		res = undefined; if elem.currentStyle then res = elem.currentStyle else if window.getComputedStyle then (res = if window.getComputedStyle.getPropertyValue then window.getComputedStyle(elem, null).getPropertyValue(prop) else window.getComputedStyle(elem)[prop])
		res

	createElement: (tag, opt) ->
		el = document.createElement(tag)
		if opt
			if opt.cName then el.className = opt.cName
			if opt.id then el.setAttribute 'id', opt.id
			if opt.inner then el.innerHTML = opt.inner
			if opt.appendTo then opt.appendTo.appendChild el
		el
app.factory '$$', helperFactory

              
            
!
999px

Console