ImageGrid/Viewer/experiments/native-scroll-ribbon-infinite.html
Alex A. Naanou 5f47d6da7b restructured the repo moving the legacy out of the way...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2020-08-28 17:32:02 +03:00

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 : -->