function getAsync(url) { return new Promise(function(resolve, reject) { var img = new Image(); img.onload = resolve; img.onerror = reject; img.setAttribute("src", url); }) } ImageZoomPreview = Ractive.extend({ el: 'body', append: true, template: '#izp-tmpl', oninit: function() { var self = this; }, onrender: function() { var self = this; ImageZoomPreview.instance = self; self.preview = self.find(".imageZoomPreview"); } }) ImageZoom = Ractive.extend({ zoom: 4, maxZoom: 8, minZoom: 1, append: true, template: '#iz-tmpl', _mouseMoveTimerInterval: 50, onteardown: function() { var self = this; clearInterval(self.onMouseMoveTimerTimeout) }, oninit: function() { var self = this; if (!ImageZoomPreview.instance) { new ImageZoomPreview({ data: self.get() }) } self.set('preview_width', self.get('preview_width') || 360) self.set('preview_height', self.get('preview_height') || 360) self.set('onMouseOver', false); self.set('onZoomActivated', false); self.on({ onThumbMouseClick: function(e) { self.set('onMouseOver', false); self.set('onZoomActivated', false); ImageZoomPreview.instance.data = self.get(); ImageZoomPreview.instance.update(); }, onThumbMouseOver: function(e) { self.set('onMouseOver', true); clearTimeout(self.onMouseOverTimeout); self.onMouseOverTimeout = setTimeout(function() { if (self.get('onMouseOver') == false) return; ImageZoomPreview.instance.data = self.get(); ImageZoomPreview.instance.update(); var previewPosition = self.getPreviewPosition(); ImageZoomPreview.instance.preview.style.left = previewPosition.left + "px"; ImageZoomPreview.instance.preview.style.top = previewPosition.top + "px"; self.set('imageLoader', getAsync(self.get('source_url'))); getAsync(self.get('source_url')) .then(function(evt) { self.set('onZoomActivated', true); try { var img; if (evt.path && evt.path[0]) { img = evt.path[0]; } else { img = evt.target; } var bPreview = ImageZoomPreview.instance.preview.getBoundingClientRect(); self.maxZoom = img.width / self.get('preview_width'); if (self.zoom >= self.maxZoom) self.zoom = self.maxZoom; } catch (e) { console.log(e); } finally {} }); }, 300) clearInterval(self.onMouseMoveTimerTimeout) self.onMouseMoveTimerTimeout = setInterval(self.onMouseMoveTimer.bind(self), self._mouseMoveTimerInterval) self.onMouseMoveTimer(); }, onThumbMouseMove: function(e) { this.mouseMoveEvent = e; }, onThumbMouseOut: function(e) { self.set('onMouseOver', false) self.set('onZoomActivated', false); clearInterval(self.onMouseMoveTimerTimeout) ImageZoomPreview.instance.data = self.get(); ImageZoomPreview.instance.update(); self.set('previewVisibilityClass', 'imageZoom_off'); } }); }, onMouseMoveTimer: function() { var self = this; var e = self.mouseMoveEvent; if (!e) return; var percentageX = (e.original.offsetX || e.original.layerX) / (e.original.target.width || e.original.target.naturalWidth); var percentageY = (e.original.offsetY || e.original.layerY) / (e.original.target.height || e.original.target.naturalHeight); var obj = { x: percentageX, y: percentageY } self.setGlassPosition(obj) self.setPreviewImagePosition(obj) }, onrender: function() { var self = this; self.thumb = self.find('.imageZoom_container>img'); self.glass = self.find('.imageZoom_glass'); }, setGlassPosition: function(coords) { var self = this; var style = "" if (self.get('onMouseOver')) { var bThumb = self.thumb.getBoundingClientRect(); var gWid = Math.floor(bThumb.width / self.zoom); var gHei = Math.floor(bThumb.height / self.zoom); var gWidPer = gWid / bThumb.width; var gHeiPer = gHei / bThumb.height; var x = 0; var y = 0; if (coords.x < gWidPer / 2) x = 0; else if (coords.x > 1 - gWidPer / 2) x = (1 - gWidPer) * bThumb.width; else x = (coords.x - gWidPer / 2) * bThumb.width; if (coords.y < gHeiPer / 2) y = 0; else if (coords.y > 1 - gHeiPer / 2) y = (1 - gHeiPer) * bThumb.height; else y = (coords.y - gHeiPer / 2) * bThumb.height; style += "left:" + (x) + "px;"; style += "top:" + (y) + "px;" style += "width:" + gWid + "px;"; style += "height:" + gHei + "px"; } self.set('glassPosition', style); }, setPreviewImagePosition: function(coords) { var self = this; var style = ""; if (self.get('onMouseOver')) { var bPreview = ImageZoomPreview.instance.preview.getBoundingClientRect(); var imgWid = self.get('preview_width') * self.zoom; var imgHei = self.get('preview_height') * self.zoom; var x = -coords.x * (imgWid - self.get('preview_width')); var y = -coords.y * (imgHei - self.get('preview_height')); style += "left:" + parseInt(x) + "px;"; style += "top:" + parseInt(y) + "px;" style += "width:" + parseInt(imgWid) + "px;"; style += "height:" + parseInt(imgHei) + "px"; } ImageZoomPreview.instance.set('previewImageStyle', style); }, getPreviewPosition: function() { var self = this; var result = { left: 0, top: 0 }; var w = window, d = document, e = d.documentElement, g = d.getElementsByTagName('body')[0], x = w.innerWidth || e.clientWidth || g.clientWidth, y = w.innerHeight || e.clientHeight || g.clientHeight; if (self.get('onMouseOver')) { var bThumb = self.thumb.getBoundingClientRect(); var bPreview = { width: self.get('preview_width'), height: self.get('preview_height') } var margin = 100; result.left = bThumb.width + margin; if (bThumb.left > bPreview.width + margin) { result.left = parseInt(bThumb.left + (-bPreview.width - margin)); if (bThumb.top > bPreview.height / 2) { result.top = parseInt(bThumb.top + bThumb.height / 2 - bPreview.height / 2); } else { result.top = bThumb.top; } } else if (x - bThumb.left > bPreview.width + margin) { result.left = parseInt(bThumb.left + bThumb.width + margin); if (bThumb.top > bPreview.height / 2) { result.top = parseInt(bThumb.top + bThumb.height / 2 - bPreview.height / 2); } else { result.top = bThumb.top; } } } return result } }); Ractive.components.ImageZoom = ImageZoom; Ractive.components.ImageZoomPreview = ImageZoomPreview; var iz = new Ractive({ el: "body", template: '#main-tmpl', append: true })
<script id='main-tmpl' type='text/html'> <div class='main'> <ImageZoom thumb_url="https://dl.dropboxusercontent.com/u/97792319/527006main_farside.1600_thumb.jpg" source_url="https://dl.dropboxusercontent.com/u/97792319/527006main_farside.1600.jpg" preview_width='360' preview_height='360' /> </div> </script> <script id="izp-tmpl" type=text/html> <div class="imageZoomPreview {{#onMouseOver}}imageZoomPreview_Border{{/onMouseOver}}" style="width:{{preview_width}}px; height:{{preview_height}}px; "> {{#onMouseOver}} <img src="{{source_url}}" style="{{previewImageStyle}}"> {{/onMouseOver}} </div> </script> <script id='iz-tmpl' type='text/html'> <div class="imageZoom_container"> <img src="{{thumb_url}}" on-mousemove="onThumbMouseMove" on-mouseover="onThumbMouseOver" on-mouseout="onThumbMouseOut" on-click="onThumbMouseClick" class="imageZoom_thumb" width="{{thumb_width}}" height="{{thumb_height}}"> {{#imageLoader}} {{#pending}} <div style="position:absolute; top:0px; left:0px;"> {{#if preloaderHTML}} {{{preloaderHTML}}} {{else}} loading {{/if}} </div> {{/pending}} {{#error}} <div style="position:absolute; top:0px; left:0px; width:2px; height:120px; background-color: #FF0000"> </div> {{/error}} {{/imageLoader}} {{#onZoomActivated}} <div class="imageZoom_glass" style="{{glassPosition}}"></div> {{/onZoomActivated}} </div> </script>
.imageZoomPreview { pointer-events: none; position: fixed; top: 0px; left: 0px; width: 360; height: 360; overflow: hidden; z-index: 99999; } .imageZoomPreview img { position: absolute; } .imageZoom_container { position: relative; display: block; } .imageZoom_container .imageZoom_glass { box-sizing: border-box; pointer-events: none; width: 0px; height: 0px; position: absolute; border: solid 1px #ccc; z-index: 1000; top: 0; left: 0; } .imageZoomPreview_Border { box-shadow: 0 0 1px rgba(34, 25, 25, 0.4); } .imageZoom_fullOpaque { opacity: 1.0; } .imageZoom_opaque { opacity: 0.5; } .imageZoom_on { display: block; } .imageZoom_off { display: none; }