/* * @author ThemePunch * @link http://www.themepunch.com/ * @copyright 2017 ThemePunch */ ;(function() { var $, engine, requestFrame = window.requestAnimationFrame; window.RsAddonPanorama = function(_$, slider) { if(!engine) { engine = webGL(); if(!engine) { slider.addClass('pano-no-webgl'); return; } } if(!_$ || !slider || typeof THREE === 'undefined' || !requestFrame) return; if(!slider.find('li[data-panorama]').length) return; $ = _$; $.event.special.rsPanoramaDestroyed = {remove: function(evt) {evt.handler();}}; return new Panorama(slider); }; function Panorama(slider) { this.slider = slider; this.slides = []; this.panos = slider.find('li[data-panorama]').each(function(i) { if(i === 0) this.setAttribute('data-fstransition', 'notransition'); this.setAttribute('data-transition', 'fade'); }); slider.one('revolution.slide.onloaded', this.onLoaded.bind(this)) .one('rsPanoramaDestroyed', this.destroy.bind(this)); } Panorama.prototype = { onLoaded: function() { this.slider.one('revolution.slide.onchange', this.onReady.bind(this)); jQuery(window).on('blur.rsaddonpanorama', this.onBlur.bind(this)); }, onReady: function(e, data) { this.resize(); var $this = this; this.panos.each(function(i) { $this.slides[i] = new PanoramaSlide($(this), $this.slider, $this.width, $this.height); }); this.slider.on('revolution.slide.afterdraw', this.resize.bind(this)) .on('revolution.slide.onbeforeswap', this.beforeSwap.bind(this)) .on('revolution.slide.onafterswap', this.afterSwap.bind(this)) .on('layeraction', this.layerAction.bind(this)); var slide = $.data(data.currentslide[0], 'rsaddonpanorama'); if(slide) slide.onReset(); this.panos = null; }, beforeSwap: function(e, data) { var slide = $.data(data.currentslide[0], 'rsaddonpanorama'); if(slide && slide.controls) slide.removeEvents(); slide = $.data(data.nextslide[0], 'rsaddonpanorama'); if(slide) slide.onReset(); }, afterSwap: function(e, data) { this.currentSlide = $.data(data.currentslide[0], 'rsaddonpanorama'); if(!data.prevslide.length) return; var slide = $.data(data.prevslide[0], 'rsaddonpanorama'); if(slide) slide.paused = true; }, resize: function() { this.width = this.slider.width(); this.height = this.slider.height(); var i = this.slides.length, slide; while(i--) { slide = this.slides[i]; slide.resize(this.width, this.height); } }, onBlur: function() { var i = this.slides.length, slide; while(i--) { slide = this.slides[i]; slide.clear(); slide.canvas.trigger('mouseup').trigger('mouseleave').trigger('touchend'); } }, layerAction: function(e, data) { data = data.event; var action = data.action; if(action && action.search('panorama') !== -1 && this.currentSlide) { var perc = data.hasOwnProperty('percentage') ? data.percentage * 0.01 : false; this.currentSlide.action(action.replace('panorama_', ''), perc); } }, destroy: function() { if(this.slides) { while(this.slides.length) { this.slides[0].destroy(); this.slides.shift(); } } for(var prop in this) if(this.hasOwnProperty(prop)) delete this[prop]; } }; function PanoramaSlide(slide, slider, width, height) { var options = JSON.parse(slide.attr('data-panorama')); this.x = 0; this.y = 0; this.zoomVal = 1; this.width = width; this.height = height; this.slide = slide; this.slider = slider; this.distX = false; this.distY = false; this.image = options.image; this.controls = options.controls; this.drag = this.controls === 'drag'; this.thrw = this.controls === 'throw'; this.move = this.drag || this.thrw; this.mouse = this.controls === 'mouse'; this.click = this.controls === 'click'; this.autoplay = options.autoplay == 'true'; this.zoom = options.mousewheelZoom == 'true'; this.smoothZoom = options.smoothZoom == 'true'; var direction = options.autoplayDirection === 'forward' ? 1 : -1; this.autoplaySpeed = parseInt(options.autoplaySpeed, 10) * 0.001; this.speed = this.autoplaySpeed * direction; this.throwSpeed = parseInt(options.throwSpeed, 10) * 0.001; this.zoomMax = 2 - (parseInt(options.zoomMax, 10) * 0.01); this.zoomMin = 2 - (parseInt(options.zoomMin, 10) * 0.01); this.fov = parseInt(options.cameraFov, 10); this.onStart = this.start.bind(this); this.onZoom = this.zooming.bind(this); this.inZoom = this.zoomIn.bind(this); this.outZoom = this.zoomOut.bind(this); this.onRender = this.render.bind(this); this.onMouse = this.mouseEvent.bind(this); this.onImgChange = this.imgChange.bind(this); this.fireAction = this.onAction.bind(this); this.renderer = new THREE[engine](); this.renderer.setSize(width, height); this.camera = new THREE.PerspectiveCamera(this.fov, width / height, 1, parseInt(options.cameraFar, 10)); this.camera.target = new THREE.Vector3(0, 0, 0); var sphere = new THREE.SphereGeometry(parseInt(options.sphereRadius, 10), parseInt(options.sphereWsegments, 10), parseInt(options.sphereHsegments, 10)); this.texture = new THREE.TextureLoader(); this.texture.minFilter = THREE.LinearFilter; sphere.applyMatrix(new THREE.Matrix4().makeScale(-1, 1, 1)); this.material = new THREE.MeshBasicMaterial(); this.scene = new THREE.Scene(); this.scene.add(new THREE.Mesh(sphere, this.material)); $.data(slide[0], 'rsaddonpanorama', this); this.canvas = $(this.renderer.domElement).addClass('rsaddon-panorama').appendTo(slide.find('.slotholder')); slide.on('mouseleave', this.onLeave.bind(this)).find('.tp-withaction').each(setActions); } PanoramaSlide.prototype = { start: function() { if(!this.imgLoaded) this.imgLoaded = true; if(!this.currentSlide) return; if(this.newImage) { this.x = 0; this.y = 0; this.zoomVal = 1; this.camera.fov = this.fov; this.camera.updateProjectionMatrix(); this.newImage = false; } this.controller = false; this.paused = false; // this.slider.removeClass('rsaddon-panorama-loading'); requestFrame(this.onRender); this.addEvents(); this.running = true; this.slider.addClass('rsaddon-pano'); }, render: function() { if(this.paused || !this.camera) return; this.y = Math.max(-85, Math.min(85, this.y)); if(this.distX !== false || this.distY !== false) { if(this.distX !== false) { this.x += (this.distX - this.x) * 0.05; if(Math.abs(this.distX - this.x) < 1) { this.distX = false; clearInterval(this.actionTimer); } } else if(this.distY !== false) { this.y += (this.distY - this.y) * 0.05; if(Math.abs(this.distY - this.y) < 1) { this.distY = false; clearInterval(this.actionTimer); } } } else if(!this.controller) { if(this.autoplay) this.x += this.speed; } else if(!this.move) { var xx = (this.mouseX - this.left) / this.width, yy = (this.mouseY - this.top) / this.height; xx = xx <= 0.5 ? (1 - (xx * 2)) * -180 : ((xx - 0.5) * 2) * 180; yy = yy <= 0.5 ? (1 - (yy * 2)) * 85 : ((yy - 0.5) * 2) * -85; yy = Math.max(-85, Math.min(85, yy)); this.x += (xx - this.x) * 0.05; this.y += (yy - this.y) * 0.05; if((this.click || this.mouse) && Math.abs(xx - this.x) < 1 && Math.abs(yy - this.y) < 1) this.controller = false; } else if(this.thrw) { if(this.mousedown) { this.vx = this.x - this.oldX; this.vy = this.y - this.oldY; this.oldX = this.x; this.oldY = this.y; } else { var vx = Math.abs(this.vx), vy = Math.abs(this.vy); if(vx > 0.01) this.x += this.vx; if(vy > 0.01) this.y += this.vy; this.vx *= this.throwSpeed; this.vy *= this.throwSpeed; if(vx <= 0.01 && vy <= 0.01) this.controller = false; } } var x = THREE.Math.degToRad(this.x), y = THREE.Math.degToRad(90 - this.y); this.camera.target.x = Math.sin(y) * Math.cos(x); this.camera.target.z = Math.sin(y) * Math.sin(x); this.camera.target.y = Math.cos(y); this.camera.lookAt(this.camera.target); if(this.smoothZoom) { this.camera.fov += ((this.fov * this.zoomVal) - this.camera.fov) * 0.05; this.camera.updateProjectionMatrix(); } this.renderer.render(this.scene, this.camera); requestFrame(this.onRender); }, onLeave: function() { clearInterval(this.actionTimer); clearInterval(this.zoomTimer); }, clear: function() { clearInterval(this.actionTimer); clearInterval(this.zoomTimer); this.distX = false; this.distY = false; }, onAction: function() { this.action(this.actionType, this.actionPerc); }, action: function(tpe, perc) { var actionType, dist; switch(tpe) { case 'left': this.distX = this.x + (-180 * perc); break; case 'right': this.distX = this.x + (180 * perc); break; case 'up': dist = this.y + (85 * perc); this.distY = Math.max(-85, Math.min(85, dist)); break; case 'down': dist = this.y + (-85 * perc); this.distY = Math.max(-85, Math.min(85, dist)); break; case 'leftstart': actionType = 'left'; break; case 'rightstart': actionType = 'right'; break; case 'upstart': actionType = 'up'; break; case 'downstart': actionType = 'down'; break; case 'leftend': case 'rightend': case 'upend': case 'downend': clearInterval(this.actionTimer); break; case 'zoomin': clearInterval(this.zoomTimer); this.zooming('gestureend', 0.5); break; case 'zoomout': clearInterval(this.zoomTimer); this.zooming('gestureend', 1.5); break; case 'zoominstart': clearInterval(this.zoomTimer); this.inZoom(); this.zoomTimer = setInterval(this.inZoom, 100); break; case 'zoomoutstart': clearInterval(this.zoomTimer); this.outZoom(); this.zoomTimer = setInterval(this.outZoom, 100); break; case 'zoominend': case 'zoomoutend': clearInterval(this.zoomTimer); break; } if(actionType) { this.clear(); this.actionPerc = perc; this.actionType = actionType; this.fireAction(); this.actionTimer = setInterval(this.fireAction, 100); } }, zooming: function(e, scale) { var tpe; this.prevZoom = this.zoomVal; if(e !== 'gestureend') { tpe = e.type; scale = e.scale; } else { tpe = e; } if(tpe !== 'gestureend') { if(e.originalEvent.wheelDelta > 0 || e.originalEvent.detail < 0) this.zoomVal -= 0.05; else this.zoomVal += 0.05; } else { if(scale < 1) this.zoomVal -= 0.05; else this.zoomVal += 0.05; } this.zoomVal = Math.max(this.zoomMax, Math.min(this.zoomMin, this.zoomVal)); if(this.zoomVal === this.zoomMax || this.zoomVal === this.zoomMin) clearInterval(this.zoomTimer); if(!this.smoothZoom) { this.camera.fov = this.fov * this.zoomVal; this.camera.updateProjectionMatrix(); } return false; }, zoomIn: function() { this.zooming('gestureend', 0.5); }, zoomOut: function() { this.zooming('gestureend', 1.5); }, imgChange: function() { this.timer = setTimeout(this.onStart, 500); }, update: function(prop, val, callback) { clearTimeout(this.timer); this.removeEvents(); this.paused = true; switch(prop) { case 'image': // this.slider.addClass('rsaddon-panorama-loading'); this.newImage = true; this.material.map = this.texture.load(val, this.onImgChange); break; case 'controls': this.controls = val; if(val !== 'none') { this.drag = val === 'drag'; this.thrw = val === 'throw'; this.mouse = val === 'mouse'; this.click = val === 'click'; this.move = this.drag || this.thrw; } else { this.x = 0; this.y = 0; } break; case 'autoplay': this.autoplay = val == 'true'; break; case 'direction': var direction = val === 'forward' ? 1 : -1; this.speed = this.autoplaySpeed * direction; break; case 'zoom': if(val == 'false') { delete this.zoom; this.zoomVal = 1; this.camera.fov = this.fov; this.camera.updateProjectionMatrix(); } else { this.zoom = true; } break; case 'smooth': this.smoothZoom = val == 'true'; break; } if(prop !== 'image') this.timer = setTimeout(this.onStart, 500); }, resize: function(width, height) { this.width = width; this.height = height; this.renderer.setSize(width, height); this.camera.aspect = width / height; this.camera.updateProjectionMatrix(); }, onReset: function() { this.paused = true; this.currentSlide = true; this.x = 0; this.y = 0; this.distX = false; this.distY = false; this.zoomVal = 1; this.camera.fov = this.fov; this.camera.updateProjectionMatrix(); if(!this.imgLoaded) this.material.map = this.texture.load(this.image, this.onStart); else this.start(); }, enterMouse: function(e, triggered) { if(this.move) this.canvas.removeClass('rsaddon-panorama-drag').addClass('rsaddon-panorama-dragging'); this.clear(); this.controller = true; this.mousedown = true; var offset = this.slider.offset(); this.left = offset.left; this.top = offset.top; if(!triggered) { var touch = e.originalEvent.touches; if(touch) e = touch[0]; this.startX = e.pageX - this.left; this.startY = e.pageY - this.top; this.prevX = this.x; this.prevY = this.y; this.oldX = this.x; this.oldY = this.y; this.canvas.on('mousemove.rsaddonpanorama touchmove.rsaddonpanorama', this.onMouse); } }, mouseEvent: function(e) { this.onLeave(); if(e.type.search(/move|click/) !== -1) { if(this.move) { if(!this.controller || !this.mousedown) return; } else if(!this.controller) { this.enterMouse(e, true); } var touch = e.originalEvent.touches; if(touch) e = touch[0]; if(!this.move) { this.mouseX = e.pageX; this.mouseY = e.pageY; } else { this.x = this.prevX + ((this.startX - (e.pageX - this.left)) * 0.1); this.y = this.prevY + (((e.pageY - this.top) - this.startY) * 0.1); } } else if(e.type.search(/down|enter|start/) !== -1) { this.enterMouse(e); } else { this.canvas.off('mousemove.rsaddonpanorama touchmove.rsaddonpanorama'); if(this.drag) this.controller = false; this.mousedown = false; if(this.move) this.canvas.removeClass('rsaddon-panorama-dragging').addClass('rsaddon-panorama-drag'); } }, addEvents: function() { switch(this.controls) { case 'drag': case 'throw': this.canvas.on( 'mousedown.rsaddonpanorama ' + 'mouseup.rsaddonpanorama ' + 'mouseleave.rsaddonpanorama ' + 'touchstart.rsaddonpanorama ' + 'touchend.rsaddonpanorama ' + 'touchcancel.rsaddonpanorama', this.onMouse); break; case 'click': this.slide.on('click.rsaddonpanorama', this.onMouse); break; case 'mouse': this.slide.on('mousemove.rsaddonpanorama touchmove.rsaddonpanorama', this.onMouse); break; } if(this.zoom) this.slider.on('mousewheel.rsaddonpanorama DOMMouseScroll.rsaddonpanorama gestureend.rsaddonpanorama', this.onZoom); if(this.controls !== 'none') { if(this.move) this.canvas.addClass('rsaddon-panorama-drag'); else if(this.click) this.slide.addClass('rsaddon-panorama-click'); } }, removeEvents: function() { this.clear(); this.mousedown = false; this.controller = false; this.currentSlide = false; switch(this.controls) { case 'drag': case 'throw': this.canvas.off('.rsaddonpanorama'); break; case 'mouse': case 'click': this.slide.off('.rsaddonpanorama'); break; } this.slider.off('.rsaddonpanorama'); this.slide.removeClass('rsaddon-panorama-click'); this.canvas.removeClass('rsaddon-panorama-drag rsaddon-panorama-dragging'); }, destroy: function() { for(var prop in this) if(this.hasOwnProperty(prop)) delete this[prop]; } }; function stopBubble(e) { e.stopPropagation(); } function setActions() { var $this = jQuery(this), actions = $this.data().actions; if($this.hasClass('rspanoaction') || !Array.isArray(actions)) return; var i = actions.length, action; while(i--) { action = actions[i]; if(action.action.search('panorama') !== -1) $this.addClass('rspanoaction').on('mousedown click mousemove touchmove', stopBubble); } } function webGL() { var canvas; try { canvas = document.createElement('canvas'); canvas = !!(window.WebGLRenderingContext && (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'))); if(canvas) canvas = 'WebGLRenderer'; } catch(e) {} if(!canvas) { canvas = window.CanvasRenderingContext2D; if(canvas) canvas = 'CanvasRenderer'; } return canvas; } })();