mirror of
				https://github.com/flynx/ImageGrid.git
				synced 2025-11-04 05:10:07 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			450 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			HTML
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			450 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			HTML
		
	
	
		
			Executable File
		
	
	
	
	
<!DOCTYPE html>
 | 
						|
<html>
 | 
						|
<!--
 | 
						|
//---------------------------------------------------------------------
 | 
						|
//
 | 
						|
 | 
						|
-->
 | 
						|
 | 
						|
<style>
 | 
						|
.mark-center:after {
 | 
						|
	position: absolute;
 | 
						|
	display: block;
 | 
						|
	content: "";
 | 
						|
	width: 5px;
 | 
						|
	height: 5px;
 | 
						|
	left: 50%;
 | 
						|
	top: 50%;
 | 
						|
	border-left: solid 2px red;
 | 
						|
	border-top: solid 2px red;
 | 
						|
	margin-left: -1px;
 | 
						|
	margin-top: -1px;
 | 
						|
	opacity: 0.8;
 | 
						|
	z-index: 1;
 | 
						|
}
 | 
						|
.mark-center:before {
 | 
						|
	position: absolute;
 | 
						|
	display: block;
 | 
						|
	content: "";
 | 
						|
	width: 5px;
 | 
						|
	height: 5px;
 | 
						|
	right: 50%;
 | 
						|
	bottom: 50%;
 | 
						|
	border-bottom: solid 2px red;
 | 
						|
	border-right: solid 2px red;
 | 
						|
	margin-bottom: -1px;
 | 
						|
	margin-right: -1px;
 | 
						|
	opacity: 0.8;
 | 
						|
	z-index: 1;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* XXX appears that there is no way to hide the scrollbar on FF...
 | 
						|
*	...one way around this is to use something like iScroll/Scrolly
 | 
						|
*	on FF or where more control is needed...
 | 
						|
*/
 | 
						|
.viewer {
 | 
						|
	position: relative;
 | 
						|
	display: inline-block;
 | 
						|
	border: solid 1px gray;
 | 
						|
 | 
						|
	width: 600px;
 | 
						|
	height: 500px;
 | 
						|
 | 
						|
	overflow: hidden;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
.scaler {
 | 
						|
	position: relative;
 | 
						|
	width: 100%;
 | 
						|
	height: 100%;
 | 
						|
 | 
						|
	top: 50%;
 | 
						|
	left: 50%;
 | 
						|
	margin-top: -50%;
 | 
						|
	margin-left: -50%;
 | 
						|
 | 
						|
	transform-origin: 50% 50%;
 | 
						|
 | 
						|
	overflow-x: hidden;
 | 
						|
	overflow-y: scroll;
 | 
						|
 | 
						|
	-ms-overflow-style: none;
 | 
						|
}
 | 
						|
.scaler::-webkit-scrollbar { 
 | 
						|
    display: none; 
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/* This is to be used for:
 | 
						|
*	- vrtical positioning
 | 
						|
*	- scaling
 | 
						|
*	  (update width to fit viewer)
 | 
						|
*/
 | 
						|
.ribbon-set {
 | 
						|
	position: relative;
 | 
						|
	display: inline-block;
 | 
						|
 | 
						|
	/* This allways needs to be of viewer width, this mostly applies
 | 
						|
	* to scaling...
 | 
						|
	*/
 | 
						|
	width: 100%;
 | 
						|
 | 
						|
	padding-top: 50%;
 | 
						|
	padding-bottom: 50%;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
.ribbon-container {
 | 
						|
	position: relative;
 | 
						|
	display: block;
 | 
						|
 | 
						|
	height: 120px;
 | 
						|
	width: 100%;
 | 
						|
 | 
						|
	overflow-x: scroll;
 | 
						|
	overflow-y: hidden;
 | 
						|
 | 
						|
	-ms-overflow-style: none;
 | 
						|
}
 | 
						|
.ribbon-container::-webkit-scrollbar { 
 | 
						|
    display: none; 
 | 
						|
}
 | 
						|
.ribbon-container:before {
 | 
						|
	position: absolute;
 | 
						|
	content: attr(index);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
.ribbon {
 | 
						|
	position: relative;
 | 
						|
	display: inline-block;
 | 
						|
 | 
						|
	height: 100px;
 | 
						|
	width: auto;
 | 
						|
 | 
						|
	white-space: nowrap;
 | 
						|
	overflow: visible;
 | 
						|
 | 
						|
	background: silver;
 | 
						|
	/*box-shadow: 0px 0px 25px -10px rgba(0,0,0,0.75);*/
 | 
						|
	box-shadow: 0px 0px 25px -10px rgba(0,0,0,1);
 | 
						|
 | 
						|
	/* start/end markers... */
 | 
						|
	/*border-left: 100px solid gray;
 | 
						|
	border-right: 100px solid gray;*/
 | 
						|
 | 
						|
	margin: 10px;
 | 
						|
 | 
						|
	margin-left: 50%;
 | 
						|
	/* XXX for some reason this does not work as expected */
 | 
						|
	margin-right: 50%;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
.image {
 | 
						|
	position: relative;
 | 
						|
	display: inline-block;
 | 
						|
 | 
						|
	width: 100px;
 | 
						|
	height: 100px;
 | 
						|
 | 
						|
	outline: solid blue 1px;
 | 
						|
 | 
						|
	background: silver;
 | 
						|
}
 | 
						|
.image:after {
 | 
						|
	content: attr(index);
 | 
						|
	opacity: 0.5;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
</style>
 | 
						|
 | 
						|
<script src="../ext-lib/jquery.js"></script>
 | 
						|
<script src="../ext-lib/jquery-ui.js"></script>
 | 
						|
 | 
						|
<script src="../ext-lib/velocity.min.js"></script>
 | 
						|
 | 
						|
<script src="../lib/jli.js"></script>
 | 
						|
 | 
						|
<script>
 | 
						|
 | 
						|
var scale = function(){
 | 
						|
	var s = /scale\(([^\)]+)\)/.exec($('.scaler')[0].style.transform)
 | 
						|
	return s ? parseFloat(s.pop()) : 1
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
// XXX when setting origin at scales different from 1, we'll need to 
 | 
						|
//	adjust offset to compensate for the shift... 
 | 
						|
// XXX one other simplification might be adding a new element specifically
 | 
						|
//	dedicated to scaling...
 | 
						|
var centerOrigin = function(){
 | 
						|
	var H = $('.viewer').height()
 | 
						|
	var s = $('.viewer')[0].scrollTop
 | 
						|
 | 
						|
	$('.ribbon-set').css({
 | 
						|
		'transform-origin': '50% '+ (s + H/2) +'px'
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
// XXX these accumolate errors...
 | 
						|
var zoomIn = function(c){
 | 
						|
	c = c || 1.2
 | 
						|
 | 
						|
	centerOrigin()
 | 
						|
	$('.scaler')
 | 
						|
		.velocity('stop')
 | 
						|
		.velocity({
 | 
						|
			scale: '*='+c,
 | 
						|
 | 
						|
			width: '/='+c,
 | 
						|
			height: '/='+c,
 | 
						|
			'margin-left': '/='+c,
 | 
						|
			'margin-top': '/='+c,
 | 
						|
		}, {
 | 
						|
			duration: 300,
 | 
						|
			easing: 'linear',
 | 
						|
		})
 | 
						|
}
 | 
						|
var zoomOut = function(c){
 | 
						|
	c = c || 1.2
 | 
						|
 | 
						|
	centerOrigin()
 | 
						|
	$('.scaler')
 | 
						|
		.velocity('stop')
 | 
						|
		.velocity({
 | 
						|
			scale: '/='+c,
 | 
						|
 | 
						|
			width: '*='+c,
 | 
						|
			height: '*='+c,
 | 
						|
			'margin-left': '*='+c,
 | 
						|
			'margin-top': '*='+c,
 | 
						|
		}, {
 | 
						|
			duration: 300,
 | 
						|
			easing: 'linear',
 | 
						|
		})
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
//	items			- list of items, each item must be make(..) compatible
 | 
						|
//						...this can also be a function and return multiple 
 | 
						|
//						items (XXX)
 | 
						|
//	make			- item DOM constructor
 | 
						|
//
 | 
						|
// Options:
 | 
						|
// 	container		- element that actually contains the items (default: 'this')
 | 
						|
//	direction		- scroll direction (default: 'vertical') 
 | 
						|
//	threshold		- 
 | 
						|
//
 | 
						|
// XXX horizontal scroll is still buggy -- mostly in thresholds...
 | 
						|
var makeScrollHandler = function(items, make, options){
 | 
						|
	options = options || {}
 | 
						|
 | 
						|
	var direction = options.direction || 'vertical'
 | 
						|
	//var threshold = options.threshold || 
 | 
						|
	var _container = options.container || 'this'
 | 
						|
 | 
						|
	// XXX should we do an initial load here???
 | 
						|
 | 
						|
	return function(evt){
 | 
						|
		var container = _container == 'this' ? 
 | 
						|
				this
 | 
						|
			: typeof(_container) == typeof('str') ? 
 | 
						|
				this.querySelector(_container) 
 | 
						|
			: _container
 | 
						|
 | 
						|
		if(direction == 'vertical'){
 | 
						|
			var size = this.scrollHeight 
 | 
						|
			var offset = this.scrollTop
 | 
						|
			var visible_size = this.offsetHeight
 | 
						|
 | 
						|
			var elem_scroll_attr = 'scrollTop'
 | 
						|
			var elem_offset_attr = 'offsetTop'
 | 
						|
			var elem_size_attr = 'offsetHeight'
 | 
						|
 | 
						|
		} else {
 | 
						|
			var size = this.scrollWidth
 | 
						|
			var offset = this.scrollLeft
 | 
						|
			var visible_size = this.offsetWidth
 | 
						|
 | 
						|
			var elem_scroll_attr = 'scrollLeft'
 | 
						|
			var elem_offset_attr = 'offsetLeft'
 | 
						|
			var elem_size_attr = 'offsetWidth'
 | 
						|
		}
 | 
						|
 | 
						|
		// XXX
 | 
						|
		var threshold = visible_size
 | 
						|
 | 
						|
		var dom_items = container.children
 | 
						|
 | 
						|
		// head limit -- add items to the head...
 | 
						|
		if(offset < threshold){
 | 
						|
			var i = parseInt(dom_items[0].getAttribute('index')) - 1
 | 
						|
			var e = items instanceof Function ? 
 | 
						|
				items(i) 
 | 
						|
				// XXX make this support multiple items...
 | 
						|
				: items[i]
 | 
						|
 | 
						|
			// make the item(s)...
 | 
						|
			if(e){
 | 
						|
				// XXX need to account for situations where the whole thing is replaced...
 | 
						|
				var c = dom_items[0]
 | 
						|
				var pre = c[elem_offset_attr]
 | 
						|
 | 
						|
				container.prepend(make(e))
 | 
						|
 | 
						|
				// compensate offset for added items...
 | 
						|
				var d = c[elem_offset_attr] - pre
 | 
						|
				// XXX need to do this only if the browser is not compensating...
 | 
						|
				if(direction == 'horizontal'){
 | 
						|
					this[elem_scroll_attr] += d 
 | 
						|
				}
 | 
						|
 | 
						|
				// remove hidden items from tail...
 | 
						|
				var t = offset + visible_size + threshold
 | 
						|
				;[].slice.call(dom_items)
 | 
						|
					// XXX add threshold / items-to-keep-offscreen limit ...
 | 
						|
					// XXX this is wrong for horizontal scroll...
 | 
						|
					.filter(function(e){ return e[elem_offset_attr] > t })
 | 
						|
					// XXX can we remove these in one go???
 | 
						|
					.forEach(function(e){ e.remove() })
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// tail limit -- add items to the tail...
 | 
						|
		if( size - (offset + visible_size) < threshold ){
 | 
						|
			var i = parseInt(dom_items[dom_items.length-1].getAttribute('index')) + 1
 | 
						|
			var e = items instanceof Function ?
 | 
						|
				items(i) 
 | 
						|
				// XXX make this support multiple items...
 | 
						|
				: items[i]
 | 
						|
 | 
						|
			if(e){
 | 
						|
				container.append(make(e))
 | 
						|
 | 
						|
				//var clone = container.cloneNode(true)
 | 
						|
				//container.replaceWith(clone)
 | 
						|
 | 
						|
				// XXX need to account for situations where the whole thing is replaced...
 | 
						|
				var c = dom_items[dom_items.length-1]
 | 
						|
				var pre = c[elem_offset_attr]
 | 
						|
 | 
						|
				// remove hidden items for head...
 | 
						|
				;[].slice.call(dom_items)
 | 
						|
					// XXX add threshold / items-to-keep-offscreen limit ...
 | 
						|
					.filter(function(e){ return e[elem_offset_attr] + e[elem_size_attr] < offset })
 | 
						|
					// XXX can we remove these in one go???
 | 
						|
					.forEach(function(e){ e.remove() })
 | 
						|
 | 
						|
				// compensate offset for removed items...
 | 
						|
				var d = c[elem_offset_attr] - pre
 | 
						|
				// XXX need to do this only if the browser is not compensating...
 | 
						|
				if(direction == 'horizontal'){
 | 
						|
					this[elem_scroll_attr] += d 
 | 
						|
				}
 | 
						|
 | 
						|
				//container.replaceWith(container)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
var setup = function(){
 | 
						|
	var H = $('.viewer').height()
 | 
						|
	var W = $('.viewer').width()
 | 
						|
 | 
						|
	var ribbon_set = $('.ribbon-set')[0]
 | 
						|
 | 
						|
 | 
						|
	// XXX need to calculate this considering scale...
 | 
						|
	var threshold = 300
 | 
						|
	var ribbon_count = 10
 | 
						|
	var image_count = 10
 | 
						|
 | 
						|
 | 
						|
	var ribbon_container = document.createElement('div')
 | 
						|
	ribbon_container.classList.add('ribbon-container')
 | 
						|
	var ribbon = document.createElement('div')
 | 
						|
	ribbon.classList.add('ribbon')
 | 
						|
	var image = document.createElement('div')
 | 
						|
	image.classList.add('image')
 | 
						|
 | 
						|
	var makeImage = function(n){
 | 
						|
		var i = image.cloneNode()
 | 
						|
		i.setAttribute('index', n)
 | 
						|
		return i
 | 
						|
	}
 | 
						|
	var makeRibbon = function(n){
 | 
						|
		var r = ribbon.cloneNode() 
 | 
						|
		for(var i=0; i < image_count; i++){
 | 
						|
			r.appendChild(makeImage(i))
 | 
						|
		}
 | 
						|
 | 
						|
		var rc = ribbon_container.cloneNode()
 | 
						|
		rc.appendChild(r)
 | 
						|
		rc.setAttribute('index', n)
 | 
						|
 | 
						|
		$(rc).scroll(makeScrollHandler(
 | 
						|
			function(n){ return n >= 0 ? n : undefined },
 | 
						|
			makeImage,
 | 
						|
			{
 | 
						|
				container: r, 
 | 
						|
				direction: 'horizontal',
 | 
						|
				threshold: 300,
 | 
						|
			}))
 | 
						|
 | 
						|
		return rc
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	var fragment = document.createDocumentFragment()
 | 
						|
	for(var i=0; i < ribbon_count; i++){
 | 
						|
		fragment.appendChild(makeRibbon(i))
 | 
						|
	}
 | 
						|
	ribbon_set.appendChild(fragment)
 | 
						|
 | 
						|
 | 
						|
	// set margins to be parant and not content dependant...
 | 
						|
	$('.scaler')
 | 
						|
		.velocity({
 | 
						|
			'margin-left': -W/2,
 | 
						|
			'margin-top': -H/2,
 | 
						|
		}, 0)
 | 
						|
		.scroll(makeScrollHandler(
 | 
						|
			function(n){ return n >= 0 ? n : undefined },
 | 
						|
			makeRibbon,
 | 
						|
			{ 
 | 
						|
				container: ribbon_set,
 | 
						|
				threshold: 300,
 | 
						|
			}))
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
$(function(){
 | 
						|
	setup()
 | 
						|
})
 | 
						|
 | 
						|
</script>
 | 
						|
 | 
						|
<body>
 | 
						|
 | 
						|
<div class="viewer mark-center">
 | 
						|
	<div class="scaler">
 | 
						|
		<div class="ribbon-set">
 | 
						|
		</div>
 | 
						|
	</div>
 | 
						|
</div>
 | 
						|
 | 
						|
</body>
 | 
						|
</html>
 | 
						|
<!-- vim:set sw=4 ts=4 : -->
 |