How to stop parallax scrolling when at the top of viewport - javascript
I have a mountain parallax scene with four images within a container that parallax scroll when the container is visible within the viewport at the bottom of the screen. The animation works but the parallax starts to reverse when the container begins to leave the viewport at the top of the screen. You can see the working example here: https://jsfiddle.net/breonwilliams/n2tspLzk/12/
The script for the parallax is below. How can I stop the parallax scroll when the container div leaves the viewport?
/**
* Creates an IntersectionObserver and starts observing all elements found using the selector.
*
* #param {String} selector: Selector used to find all target elements
* #param {Number[]} threshold: Array of intersection ratios, at which the callback is executed
* #param {Function} callback: Callback executed for each threshold
*/
function observe(selector, threshold, callback) {
const elements = document.querySelectorAll(selector);
const options = {
rootMargin: '-100px',
threshold: threshold,
};
const observer = new IntersectionObserver(callback, options);
for (const element of elements) {
observer.observe(element);
}
}
/**
* Creates a CSS translateY value.
*
* #param {Number} ratio: A number between 0 and 1
* #param {String} total: A valid CSS number and unit (10px, 100%, 30vh, …)
* #return {String} The CSS translateY value.
*/
function translateY(ratio, total) {
return `translateY(calc(-${ratio} * ${total})`;
}
/**
* Callback executed for the box elements
*
* #see https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
*
* #param {IntersectionObserverEntry[]} entries: Intersection Observer Entries
* #param {IntersectionObserver} observer: Intersection Observer
*/
function boxParallax(entries, observer) {
for (const entry of entries) {
if (entry.isIntersecting) {
entry.target.style.transform = translateY(entry.intersectionRatio, '-140px');
}
}
}
function boxParallax_two(entries, observer) {
for (const entry of entries) {
if (entry.isIntersecting) {
entry.target.style.transform = translateY(entry.intersectionRatio, '-270px');
}
}
}
function boxParallax_three(entries, observer) {
for (const entry of entries) {
if (entry.isIntersecting) {
entry.target.style.transform = translateY(entry.intersectionRatio, '-300px');
}
}
}
function boxParallax_four(entries, observer) {
for (const entry of entries) {
if (entry.isIntersecting) {
entry.target.style.transform = translateY(entry.intersectionRatio, '-350px');
}
}
}
/**
* Create one threshold for each intersection ratio.
*
* #return {Number[]}
*/
function createThreshold() {
const threshold = [];
for (let i = 0; i <= 1.0; i += 0.01) {
threshold.push(i);
}
return threshold;
}
const threshold = createThreshold();
observe('.two', threshold, boxParallax);
observe('.three', threshold, boxParallax_two);
observe('.four', threshold, boxParallax_three);
observe('.five', threshold, boxParallax_four);
});
/**
* Creates an IntersectionObserver and starts observing all elements found using the selector.
*
* #param {String} selector: Selector used to find all target elements
* #param {Number[]} threshold: Array of intersection ratios, at which the callback is executed
* #param {Function} callback: Callback executed for each threshold
*/
function observe(selector, threshold, callback) {
const elements = document.querySelectorAll(selector);
const options = {
rootMargin: '-100px',
threshold: threshold,
};
const observer = new IntersectionObserver(callback, options);
for (const element of elements) {
observer.observe(element);
}
}
/**
* Creates a CSS translateY value.
*
* #param {Number} ratio: A number between 0 and 1
* #param {String} total: A valid CSS number and unit (10px, 100%, 30vh, …)
* #return {String} The CSS translateY value.
*/
function translateY(ratio, total) {
return `translateY(calc(-${ratio} * ${total})`;
}
/**
* Callback executed for the box elements
*
* #see https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
*
* #param {IntersectionObserverEntry[]} entries: Intersection Observer Entries
* #param {IntersectionObserver} observer: Intersection Observer
*/
function boxParallax(entries, observer) {
for (const entry of entries) {
if (entry.isIntersecting && entry.boundingClientRect.top > 100) {
entry.target.style.transform = translateY(entry.intersectionRatio, '-140px');
}
}
}
function boxParallax_two(entries, observer) {
for (const entry of entries) {
if (entry.isIntersecting && entry.boundingClientRect.top > 100) {
entry.target.style.transform = translateY(entry.intersectionRatio, '-270px');
}
}
}
function boxParallax_three(entries, observer) {
for (const entry of entries) {
if (entry.isIntersecting && entry.boundingClientRect.top > 100) {
entry.target.style.transform = translateY(entry.intersectionRatio, '-300px');
}
}
}
function boxParallax_four(entries, observer) {
for (const entry of entries) {
if (entry.isIntersecting && entry.boundingClientRect.top > 100) {
entry.target.style.transform = translateY(entry.intersectionRatio, '-350px');
}
}
}
/**
* Create one threshold for each intersection ratio.
*
* #return {Number[]}
*/
function createThreshold() {
const threshold = [];
for (let i = 0; i <= 0.5; i += 0.01) {
threshold.push(i);
}
return threshold;
}
const threshold = createThreshold();
observe('.two', threshold, boxParallax);
observe('.three', threshold, boxParallax_two);
observe('.four', threshold, boxParallax_three);
observe('.five', threshold, boxParallax_four);
});```
Related
Intersection observer to detect in viewport Images in the overflow scroll container not working
Intersection observer is firing event for all the image in the overflow scroll container not when they come in the view port. Using the intersection observer to lazy load images: https://deanhume.com/home/blogpost/lazy-loading-images-using-intersection-observer/10163 <ul class="product-horizontal-list"> <li> <img class="js-lazy-image centered" data-src="https://rukminim1.flixcart.com/image/312/312/j0vb3bk0/t-shirt/h/b/x/m-arek0253grey-melange-arrow-sports-original-imaeskqnukxhvhth.jpeg?q=70"></li> <li> <img class="js-lazy-image centered" data-src="https://rukminim1.flixcart.com/image/312/312/j6dxaq80/t-shirt/h/y/u/m-28771-0061blacks-levi-s-original-imaewv46jxf4wyxa.jpeg?q=70"></li> <li> <img class="js-lazy-image centered" data-src="https://rukminim1.flixcart.com/image/312/312/j7qi9ow0/t-shirt/x/r/z/m-bts026-billion-original-imaexwxvczbnfz8a.jpeg?q=70"></li> <li> <img class="js-lazy-image centered" data-src="https://rukminim1.flixcart.com/image/312/312/j4hc5u80/t-shirt/u/w/j/m-17p3dtpj3033i501-united-colors-of-benetton-original-imaevcrzqas8uwvy.jpeg?q=70"></li> <li> <img class="js-lazy-image centered" data-src="https://rukminim1.flixcart.com/image/312/312/j0vb3bk0/t-shirt/n/a/x/m-arek0255me-yellow-arrow-sports-original-imaeskqzm5hrn8hk.jpeg?q=70"></li> <li> <img class="js-lazy-image centered" data-src="https://rukminim1.flixcart.com/image/312/312/j6pctjk0/t-shirt/v/e/8/m-akss3356navy-arrow-sport-original-imaex3xgzhjvdzxu.jpeg?q=70"></li> <li> <img class="js-lazy-image centered" data-src="https://rukminim1.flixcart.com/image/312/312/j6gs6fk0/t-shirt/e/v/n/m-24608-0004blues-levi-s-original-imaewxcwweyz9fh3.jpeg?q=70"></li> </ul> // Get all of the images that are marked up to lazy load const images = document.querySelectorAll('.js-lazy-image'); const config = { // If the image gets within 50px in the Y axis, start the download. rootMargin: '0px', threshold: 0.01 }; let imageCount = images.length; let observer; // If we don't have support for intersection observer, loads the images immediately if (!('IntersectionObserver' in window)) { loadImagesImmediately(images); } else { // It is supported, load the images observer = new IntersectionObserver(onIntersection, config); // foreach() is not supported in IE for (let i = 0; i < images.length; i++) { let image = images[i]; if (image.classList.contains('js-lazy-image--handled')) { continue; } observer.observe(image); } } /** * Fetchs the image for the given URL * #param {string} url */ function fetchImage(url) { return new Promise((resolve, reject) => { const image = new Image(); image.src = url; image.onload = resolve; image.onerror = reject; }); } /** * Preloads the image * #param {object} image */ function preloadImage(image) { const src = image.dataset.src; if (!src) { return; } return fetchImage(src).then(() => { applyImage(image, src); }); } /** * Load all of the images immediately * #param {NodeListOf<Element>} images */ function loadImagesImmediately(images) { // foreach() is not supported in IE for (let i = 0; i < images.length; i++) { let image = images[i]; preloadImage(image); } } /** * Disconnect the observer */ function disconnect() { if (!observer) { return; } observer.disconnect(); } /** * On intersection * #param {array} entries */ function onIntersection(entries) { // Disconnect if we've already loaded all of the images if (imageCount === 0) { observer.disconnect(); } // Loop through the entries for (let i = 0; i < entries.length; i++) { let entry = entries[i]; // Are we in viewport? console.log('in viewport') if (entry.intersectionRatio > 0) { imageCount--; // Stop watching and load the image observer.unobserve(entry.target); preloadImage(entry.target); } } } /** * Apply the image * #param {object} img * #param {string} src */ function applyImage(img, src) { // Prevent this from being lazy loaded a second time. img.classList.add('js-lazy-image--handled'); img.src = src; img.classList.add('fade-in'); } https://jsfiddle.net/anshuPurohit/h84k9zkv/
The good news is your intersection observer is working. I haven't tested all of your methods, but I did get it to behave the way you want it to in your example fiddle. You need to set at least a min-width on the images. Since images are embedded content, their initial width is going to be 0. And the intersection observer makes its calculations before your images load, which means all of those elements will be visible to it when it runs. You also currently have your threshold set very low. Your current threshold of '0.01' means that only 1% of the element needs to be visible for the callback to execute. I would recommend setting it at '0.25' and experiment with it from there. Tl;dr. Increase your threshold, set a min-width on the image and see how it goes. One other recommendation for your image scroller, which will help maintain responsive images without resorting to assigning an explicit width to your images, is to use an intrinsic placeholder. http://daverupert.com/2015/12/intrinsic-placeholders-with-picture/
Cant change the colour of the child element
I have the following markup: I am using Piklor, a cooler picker https://github.com/jillix/piklor.js/ It is initialised by: header = pk.getElm(".selected-icon p") and the colour attributes are assigned here: header.style.color = col; However, the above doesn't work. Am I targeting the p tag right? The class of the p tag changes pragmatically in my scrip, so I can't really use the class name and can only refer to it by the parent element. If I target ".selected-icon" (without the p tag) and with a backgroundColor, e.g. header.style.backgroundColor = col; it seems to work, but not the colour of the p tag. (function (root) { /** * Piklor * Creates a new `Piklor` instance. * * #name Piklor * #function * #param {String|Element} sel The element where the color picker will live. * #param {Array} colors An array of strings representing colors. * #param {Object} options An object containing the following fields: * * - `open` (String|Element): The HTML element or query selector which will open the picker. * - `openEvent` (String): The open event (default: `"click"`). * - `style` (Object): Some style options: * - `display` (String): The display value when the picker is opened (default: `"block"`). * - `template` (String): The color item template. The `{color}` snippet will be replaced * with the color value (default: `"<div data-col=\"{color}\" style=\"background-color: {color}\"></div>"`). * - `autoclose` (Boolean): If `false`, the color picker will not be hided by default (default: `true`). * - `closeOnBlur` (Boolean): If `true`, the color picker will be closed when clicked outside of it (default: `false`). * * #return {Piklor} The `Piklor` instance. */ function Piklor(sel, colors, options) { var self = this; options = options || {}; options.open = self.getElm(options.open); options.openEvent = options.openEvent || "click"; options.style = Object(options.style); options.style.display = options.style.display || "block"; options.closeOnBlur = options.closeOnBlur || false; options.template = options.template || "<div data-col=\"{color}\" style=\"background-color: {color}\"></div>"; self.elm = self.getElm(sel); self.cbs = []; self.isOpen = true; self.colors = colors; self.options = options; self.render(); // Handle the open element and event. if (options.open) { options.open.addEventListener(options.openEvent, function (ev) { self.isOpen ? self.close() : self.open(); }); } // Click on colors self.elm.addEventListener("click", function (ev) { var col = ev.target.getAttribute("data-col"); if (!col) { return; } self.set(col); self.close(); }); if (options.closeOnBlur) { window.addEventListener("click", function (ev) { // check if we didn't click 'open' and 'color pallete' elements if (ev.target != options.open && ev.target != self.elm && self.isOpen) { self.close(); } }); } if (options.autoclose !== false) { self.close(); } } /** * getElm * Finds the HTML element. * * #name getElm * #function * #param {String|Element} el The HTML element or query selector. * #return {HTMLElement} The selected HTML element. */ Piklor.prototype.getElm = function (el) { if (typeof el === "string") { return document.querySelector(el); } return el; }; /** * render * Renders the colors. * * #name render * #function */ Piklor.prototype.render = function () { var self = this , html = "" ; self.colors.forEach(function (c) { html += self.options.template.replace(/\{color\}/g, c); }); self.elm.innerHTML = html; }; /** * close * Closes the color picker. * * #name close * #function */ Piklor.prototype.close = function () { this.elm.style.display = "none"; this.isOpen = false; }; /** * open * Opens the color picker. * * #name open * #function */ Piklor.prototype.open = function () { this.elm.style.display = this.options.style.display; this.isOpen = true; }; /** * colorChosen * Adds a new callback in the colorChosen callback buffer. * * #name colorChosen * #function * #param {Function} cb The callback function called with the selected color. */ Piklor.prototype.colorChosen = function (cb) { this.cbs.push(cb); }; /** * set * Sets the color picker color. * * #name set * #function * #param {String} c The color to set. * #param {Boolean} p If `false`, the `colorChosen` callbacks will not be called. */ Piklor.prototype.set = function (c, p) { var self = this; self.color = c; if (p === false) { return; } self.cbs.forEach(function (cb) { cb.call(self, c); }); }; root.Piklor = Piklor; })(this); window.addEventListener("load", function () { var pk = new Piklor(".color-picker", [ "#1abc9c" , "#2ecc71" , "#3498db" , "#9b59b6" , "#34495e" , "#16a085" , "#27ae60" , "#2980b9" , "#8e44ad" , "#2c3e50" , "#f1c40f" , "#e67e22" , "#e74c3c" , "#ecf0f1" , "#95a5a6" , "#f39c12" , "#d35400" , "#c0392b" , "#bdc3c7" , "#7f8c8d" ], { open: ".picker-wrapper .btn" }) , wrapperEl = pk.getElm(".picker-wrapper") , header = pk.getElm(".selected-icon p") , footer = pk.getElm("footer") ; pk.colorChosen(function (col) { wrapperEl.style.backgroundColor = col; header.style.backgroundColor = col; footer.style.backgroundColor = col; }); }); /* picker */ .color-picker { background: rgba(255, 255, 255, 0.75); padding: 10px; border: 1px solid rgba(203, 203, 203, 0.6); border-radius: 2px; } .color-picker > div { width: 40px; display: inline-block; height: 40px; margin: 5px; border-radius: 100%; opacity: 0.7; } .picker-wrapper { padding: 20px; } .color-picker > div:hover { opacity: 1; } <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script> <span class="selected-icon"><p class="icon-eight">Hello</i></span> <div class="picker-wrapper"> <button class="btn btn-default">Select color</button> <div class="color-picker"> </div> </div>
Okay. If that is the case, your issue is the p tag is not closed. <span class="selected-icon"><p class="icon-eight">Hello</i></span> You have an "i" tag trying to close the leading "p" tag. Change that and you should be good to go. Also change header.style.backgroundColor = col; To header.style.color = col; To achieve the desired result.
Adding an ID or class to Marker using markerwithlabel class
var markers = new MarkerWithLabel({ position: point, icon: imagePath1, map: map, draggable: false, labelContent: image_count, labelAnchor: new google.maps.Point(10, 30), labelClass: "labels", // the CSS class for the label labelInBackground: false, id: "markerId", title : contentVal, }); alert("Id is::"+$("#"+markers.id).val()); When I try to get Id, it is undefined
I too hit this roadblock. I reviewed the source code for MarkerWithLabel, and it does not support setting the ID or Class. But you can add support by changing just 3 lines of code. Below is the modified code, with my added "ID" support. It works as normal, but if you add a .id property when creating a new MarkerWithLabel (as you did above), it will properly set the ID. New markerswithlabel.js code — Added ID Support /** * #name MarkerWithLabel for V3 * #version 1.1.10 [April 8, 2014] * #author Gary Little (inspired by code from Marc Ridey of Google). * #copyright Copyright 2012 Gary Little [gary at luxcentral.com] * #fileoverview MarkerWithLabel extends the Google Maps JavaScript API V3 * <code>google.maps.Marker</code> class. * <p> * MarkerWithLabel allows you to define markers with associated labels. As you would expect, * if the marker is draggable, so too will be the label. In addition, a marker with a label * responds to all mouse events in the same manner as a regular marker. It also fires mouse * events and "property changed" events just as a regular marker would. Version 1.1 adds * support for the raiseOnDrag feature introduced in API V3.3. * <p> * If you drag a marker by its label, you can cancel the drag and return the marker to its * original position by pressing the <code>Esc</code> key. This doesn't work if you drag the marker * itself because this feature is not (yet) supported in the <code>google.maps.Marker</code> class. */ /*! * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*jslint browser:true */ /*global document,google */ /** * #param {Function} childCtor Child class. * #param {Function} parentCtor Parent class. * #private */ function inherits(childCtor, parentCtor) { /* #constructor */ function tempCtor() {} tempCtor.prototype = parentCtor.prototype; childCtor.superClass_ = parentCtor.prototype; childCtor.prototype = new tempCtor(); /* #override */ childCtor.prototype.constructor = childCtor; } /** * This constructor creates a label and associates it with a marker. * It is for the private use of the MarkerWithLabel class. * #constructor * #param {Marker} marker The marker with which the label is to be associated. * #param {string} crossURL The URL of the cross image =. * #param {string} handCursor The URL of the hand cursor. * #private */ function MarkerLabel_(id, marker, crossURL, handCursorURL) { this.marker_ = marker; this.handCursorURL_ = marker.handCursorURL; this.labelDiv_ = document.createElement("div"); ////////////////////////////////////////////////////////// // New - ID support ////////////////////////////////////////////////////////// if (typeof id !== 'undefined') this.labelDiv_.id = id; this.labelDiv_.style.cssText = "position: absolute; overflow: hidden;"; // Get the DIV for the "X" to be displayed when the marker is raised. this.crossDiv_ = MarkerLabel_.getSharedCross(crossURL); } inherits(MarkerLabel_, google.maps.OverlayView); /** * Returns the DIV for the cross used when dragging a marker when the * raiseOnDrag parameter set to true. One cross is shared with all markers. * #param {string} crossURL The URL of the cross image =. * #private */ MarkerLabel_.getSharedCross = function (crossURL) { var div; if (typeof MarkerLabel_.getSharedCross.crossDiv === "undefined") { div = document.createElement("img"); div.style.cssText = "position: absolute; z-index: 1000002; display: none;"; // Hopefully Google never changes the standard "X" attributes: div.style.marginLeft = "-8px"; div.style.marginTop = "-9px"; div.src = crossURL; MarkerLabel_.getSharedCross.crossDiv = div; } return MarkerLabel_.getSharedCross.crossDiv; }; /** * Adds the DIV representing the label to the DOM. This method is called * automatically when the marker's <code>setMap</code> method is called. * #private */ MarkerLabel_.prototype.onAdd = function () { var me = this; var cMouseIsDown = false; var cDraggingLabel = false; var cSavedZIndex; var cLatOffset, cLngOffset; var cIgnoreClick; var cRaiseEnabled; var cStartPosition; var cStartCenter; // Constants: var cRaiseOffset = 20; var cDraggingCursor = "url(" + this.handCursorURL_ + ")"; // Stops all processing of an event. // var cAbortEvent = function (e) { if (e.preventDefault) { e.preventDefault(); } e.cancelBubble = true; if (e.stopPropagation) { e.stopPropagation(); } }; var cStopBounce = function () { me.marker_.setAnimation(null); }; this.getPanes().overlayMouseTarget.appendChild(this.labelDiv_); // One cross is shared with all markers, so only add it once: if (typeof MarkerLabel_.getSharedCross.processed === "undefined") { this.getPanes().overlayMouseTarget.appendChild(this.crossDiv_); MarkerLabel_.getSharedCross.processed = true; } this.listeners_ = [ google.maps.event.addDomListener(this.labelDiv_, "mouseover", function (e) { if (me.marker_.getDraggable() || me.marker_.getClickable()) { this.style.cursor = "pointer"; google.maps.event.trigger(me.marker_, "mouseover", e); } }), google.maps.event.addDomListener(this.labelDiv_, "mouseout", function (e) { if ((me.marker_.getDraggable() || me.marker_.getClickable()) && !cDraggingLabel) { this.style.cursor = me.marker_.getCursor(); google.maps.event.trigger(me.marker_, "mouseout", e); } }), google.maps.event.addDomListener(this.labelDiv_, "mousedown", function (e) { cDraggingLabel = false; if (me.marker_.getDraggable()) { cMouseIsDown = true; this.style.cursor = cDraggingCursor; } if (me.marker_.getDraggable() || me.marker_.getClickable()) { google.maps.event.trigger(me.marker_, "mousedown", e); cAbortEvent(e); // Prevent map pan when starting a drag on a label } }), google.maps.event.addDomListener(document, "mouseup", function (mEvent) { var position; if (cMouseIsDown) { cMouseIsDown = false; me.eventDiv_.style.cursor = "pointer"; google.maps.event.trigger(me.marker_, "mouseup", mEvent); } if (cDraggingLabel) { if (cRaiseEnabled) { // Lower the marker & label position = me.getProjection().fromLatLngToDivPixel(me.marker_.getPosition()); position.y += cRaiseOffset; me.marker_.setPosition(me.getProjection().fromDivPixelToLatLng(position)); // This is not the same bouncing style as when the marker portion is dragged, // but it will have to do: try { // Will fail if running Google Maps API earlier than V3.3 me.marker_.setAnimation(google.maps.Animation.BOUNCE); setTimeout(cStopBounce, 1406); } catch (e) {} } me.crossDiv_.style.display = "none"; me.marker_.setZIndex(cSavedZIndex); cIgnoreClick = true; // Set flag to ignore the click event reported after a label drag cDraggingLabel = false; mEvent.latLng = me.marker_.getPosition(); google.maps.event.trigger(me.marker_, "dragend", mEvent); } }), google.maps.event.addListener(me.marker_.getMap(), "mousemove", function (mEvent) { var position; if (cMouseIsDown) { if (cDraggingLabel) { // Change the reported location from the mouse position to the marker position: mEvent.latLng = new google.maps.LatLng(mEvent.latLng.lat() - cLatOffset, mEvent.latLng.lng() - cLngOffset); position = me.getProjection().fromLatLngToDivPixel(mEvent.latLng); if (cRaiseEnabled) { me.crossDiv_.style.left = position.x + "px"; me.crossDiv_.style.top = position.y + "px"; me.crossDiv_.style.display = ""; position.y -= cRaiseOffset; } me.marker_.setPosition(me.getProjection().fromDivPixelToLatLng(position)); if (cRaiseEnabled) { // Don't raise the veil; this hack needed to make MSIE act properly me.eventDiv_.style.top = (position.y + cRaiseOffset) + "px"; } google.maps.event.trigger(me.marker_, "drag", mEvent); } else { // Calculate offsets from the click point to the marker position: cLatOffset = mEvent.latLng.lat() - me.marker_.getPosition().lat(); cLngOffset = mEvent.latLng.lng() - me.marker_.getPosition().lng(); cSavedZIndex = me.marker_.getZIndex(); cStartPosition = me.marker_.getPosition(); cStartCenter = me.marker_.getMap().getCenter(); cRaiseEnabled = me.marker_.get("raiseOnDrag"); cDraggingLabel = true; me.marker_.setZIndex(1000000); // Moves the marker & label to the foreground during a drag mEvent.latLng = me.marker_.getPosition(); google.maps.event.trigger(me.marker_, "dragstart", mEvent); } } }), google.maps.event.addDomListener(document, "keydown", function (e) { if (cDraggingLabel) { if (e.keyCode === 27) { // Esc key cRaiseEnabled = false; me.marker_.setPosition(cStartPosition); me.marker_.getMap().setCenter(cStartCenter); google.maps.event.trigger(document, "mouseup", e); } } }), google.maps.event.addDomListener(this.labelDiv_, "click", function (e) { if (me.marker_.getDraggable() || me.marker_.getClickable()) { if (cIgnoreClick) { // Ignore the click reported when a label drag ends cIgnoreClick = false; } else { google.maps.event.trigger(me.marker_, "click", e); cAbortEvent(e); // Prevent click from being passed on to map } } }), google.maps.event.addDomListener(this.labelDiv_, "dblclick", function (e) { if (me.marker_.getDraggable() || me.marker_.getClickable()) { google.maps.event.trigger(me.marker_, "dblclick", e); cAbortEvent(e); // Prevent map zoom when double-clicking on a label } }), google.maps.event.addListener(this.marker_, "dragstart", function (mEvent) { if (!cDraggingLabel) { cRaiseEnabled = this.get("raiseOnDrag"); } }), google.maps.event.addListener(this.marker_, "drag", function (mEvent) { if (!cDraggingLabel) { if (cRaiseEnabled) { me.setPosition(cRaiseOffset); // During a drag, the marker's z-index is temporarily set to 1000000 to // ensure it appears above all other markers. Also set the label's z-index // to 1000000 (plus or minus 1 depending on whether the label is supposed // to be above or below the marker). me.labelDiv_.style.zIndex = 1000000 + (this.get("labelInBackground") ? -1 : +1); } } }), google.maps.event.addListener(this.marker_, "dragend", function (mEvent) { if (!cDraggingLabel) { if (cRaiseEnabled) { me.setPosition(0); // Also restores z-index of label } } }), google.maps.event.addListener(this.marker_, "position_changed", function () { me.setPosition(); }), google.maps.event.addListener(this.marker_, "zindex_changed", function () { me.setZIndex(); }), google.maps.event.addListener(this.marker_, "visible_changed", function () { me.setVisible(); }), google.maps.event.addListener(this.marker_, "labelvisible_changed", function () { me.setVisible(); }), google.maps.event.addListener(this.marker_, "title_changed", function () { me.setTitle(); }), google.maps.event.addListener(this.marker_, "labelcontent_changed", function () { me.setContent(); }), google.maps.event.addListener(this.marker_, "labelanchor_changed", function () { me.setAnchor(); }), google.maps.event.addListener(this.marker_, "labelclass_changed", function () { me.setStyles(); }), google.maps.event.addListener(this.marker_, "labelstyle_changed", function () { me.setStyles(); }) ]; }; /** * Removes the DIV for the label from the DOM. It also removes all event handlers. * This method is called automatically when the marker's <code>setMap(null)</code> * method is called. * #private */ MarkerLabel_.prototype.onRemove = function () { var i; this.labelDiv_.parentNode.removeChild(this.labelDiv_); // Remove event listeners: for (i = 0; i < this.listeners_.length; i++) { google.maps.event.removeListener(this.listeners_[i]); } }; /** * Draws the label on the map. * #private */ MarkerLabel_.prototype.draw = function () { this.setContent(); this.setTitle(); this.setStyles(); }; /** * Sets the content of the label. * The content can be plain text or an HTML DOM node. * #private */ MarkerLabel_.prototype.setContent = function () { var content = this.marker_.get("labelContent"); if (typeof content.nodeType === "undefined") { this.labelDiv_.innerHTML = content; } else { this.labelDiv_.innerHTML = ""; // Remove current content this.labelDiv_.appendChild(content); } }; /** * Sets the content of the tool tip for the label. It is * always set to be the same as for the marker itself. * #private */ MarkerLabel_.prototype.setTitle = function () { this.labelDiv_.title = this.marker_.getTitle() || ""; }; /** * Sets the style of the label by setting the style sheet and applying * other specific styles requested. * #private */ MarkerLabel_.prototype.setStyles = function () { var i, labelStyle; // Apply style values from the style sheet defined in the labelClass parameter: this.labelDiv_.className = this.marker_.get("labelClass"); // Clear existing inline style values: this.labelDiv_.style.cssText = ""; // Apply style values defined in the labelStyle parameter: labelStyle = this.marker_.get("labelStyle"); for (i in labelStyle) { if (labelStyle.hasOwnProperty(i)) { this.labelDiv_.style[i] = labelStyle[i]; } } this.setMandatoryStyles(); }; /** * Sets the mandatory styles to the DIV representing the label as well as to the * associated event DIV. This includes setting the DIV position, z-index, and visibility. * #private */ MarkerLabel_.prototype.setMandatoryStyles = function () { this.labelDiv_.style.position = "absolute"; this.labelDiv_.style.overflow = "hidden"; // Make sure the opacity setting causes the desired effect on MSIE: if (typeof this.labelDiv_.style.opacity !== "undefined" && this.labelDiv_.style.opacity !== "") { this.labelDiv_.style.MsFilter = "\"progid:DXImageTransform.Microsoft.Alpha(opacity=" + (this.labelDiv_.style.opacity * 100) + ")\""; this.labelDiv_.style.filter = "alpha(opacity=" + (this.labelDiv_.style.opacity * 100) + ")"; } this.setAnchor(); this.setPosition(); // This also updates z-index, if necessary. this.setVisible(); }; /** * Sets the anchor point of the label. * #private */ MarkerLabel_.prototype.setAnchor = function () { var anchor = this.marker_.get("labelAnchor"); this.labelDiv_.style.marginLeft = -anchor.x + "px"; this.labelDiv_.style.marginTop = -anchor.y + "px"; }; /** * Sets the position of the label. The z-index is also updated, if necessary. * #private */ MarkerLabel_.prototype.setPosition = function (yOffset) { var position = this.getProjection().fromLatLngToDivPixel(this.marker_.getPosition()); if (typeof yOffset === "undefined") { yOffset = 0; } this.labelDiv_.style.left = Math.round(position.x) + "px"; this.labelDiv_.style.top = Math.round(position.y - yOffset) + "px"; this.setZIndex(); }; /** * Sets the z-index of the label. If the marker's z-index property has not been defined, the z-index * of the label is set to the vertical coordinate of the label. This is in keeping with the default * stacking order for Google Maps: markers to the south are in front of markers to the north. * #private */ MarkerLabel_.prototype.setZIndex = function () { var zAdjust = (this.marker_.get("labelInBackground") ? -1 : +1); if (typeof this.marker_.getZIndex() === "undefined") { this.labelDiv_.style.zIndex = parseInt(this.labelDiv_.style.top, 10) + zAdjust; } else { this.labelDiv_.style.zIndex = this.marker_.getZIndex() + zAdjust; } }; /** * Sets the visibility of the label. The label is visible only if the marker itself is * visible (i.e., its visible property is true) and the labelVisible property is true. * #private */ MarkerLabel_.prototype.setVisible = function () { if (this.marker_.get("labelVisible")) { this.labelDiv_.style.display = this.marker_.getVisible() ? "block" : "none"; } else { this.labelDiv_.style.display = "none"; } }; /** * #name MarkerWithLabelOptions * #class This class represents the optional parameter passed to the {#link MarkerWithLabel} constructor. * The properties available are the same as for <code>google.maps.Marker</code> with the addition * of the properties listed below. To change any of these additional properties after the labeled * marker has been created, call <code>google.maps.Marker.set(propertyName, propertyValue)</code>. * <p> * When any of these properties changes, a property changed event is fired. The names of these * events are derived from the name of the property and are of the form <code>propertyname_changed</code>. * For example, if the content of the label changes, a <code>labelcontent_changed</code> event * is fired. * <p> * #property {string|Node} [labelContent] The content of the label (plain text or an HTML DOM node). * #property {Point} [labelAnchor] By default, a label is drawn with its anchor point at (0,0) so * that its top left corner is positioned at the anchor point of the associated marker. Use this * property to change the anchor point of the label. For example, to center a 50px-wide label * beneath a marker, specify a <code>labelAnchor</code> of <code>google.maps.Point(25, 0)</code>. * (Note: x-values increase to the right and y-values increase to the top.) * #property {string} [labelClass] The name of the CSS class defining the styles for the label. * Note that style values for <code>position</code>, <code>overflow</code>, <code>top</code>, * <code>left</code>, <code>zIndex</code>, <code>display</code>, <code>marginLeft</code>, and * <code>marginTop</code> are ignored; these styles are for internal use only. * #property {Object} [labelStyle] An object literal whose properties define specific CSS * style values to be applied to the label. Style values defined here override those that may * be defined in the <code>labelClass</code> style sheet. If this property is changed after the * label has been created, all previously set styles (except those defined in the style sheet) * are removed from the label before the new style values are applied. * Note that style values for <code>position</code>, <code>overflow</code>, <code>top</code>, * <code>left</code>, <code>zIndex</code>, <code>display</code>, <code>marginLeft</code>, and * <code>marginTop</code> are ignored; these styles are for internal use only. * #property {boolean} [labelInBackground] A flag indicating whether a label that overlaps its * associated marker should appear in the background (i.e., in a plane below the marker). * The default is <code>false</code>, which causes the label to appear in the foreground. * #property {boolean} [labelVisible] A flag indicating whether the label is to be visible. * The default is <code>true</code>. Note that even if <code>labelVisible</code> is * <code>true</code>, the label will <i>not</i> be visible unless the associated marker is also * visible (i.e., unless the marker's <code>visible</code> property is <code>true</code>). * #property {boolean} [raiseOnDrag] A flag indicating whether the label and marker are to be * raised when the marker is dragged. The default is <code>true</code>. If a draggable marker is * being created and a version of Google Maps API earlier than V3.3 is being used, this property * must be set to <code>false</code>. * #property {boolean} [optimized] A flag indicating whether rendering is to be optimized for the * marker. <b>Important: The optimized rendering technique is not supported by MarkerWithLabel, * so the value of this parameter is always forced to <code>false</code>. * #property {string} [crossImage="http://maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png"] * The URL of the cross image to be displayed while dragging a marker. * #property {string} [handCursor="http://maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur"] * The URL of the cursor to be displayed while dragging a marker. */ /** * Creates a MarkerWithLabel with the options specified in {#link MarkerWithLabelOptions}. * #constructor * #param {MarkerWithLabelOptions} [opt_options] The optional parameters. */ function MarkerWithLabel(opt_options) { opt_options = opt_options || {}; opt_options.labelContent = opt_options.labelContent || ""; opt_options.labelAnchor = opt_options.labelAnchor || new google.maps.Point(0, 0); opt_options.labelClass = opt_options.labelClass || "markerLabels"; opt_options.labelStyle = opt_options.labelStyle || {}; opt_options.labelInBackground = opt_options.labelInBackground || false; if (typeof opt_options.labelVisible === "undefined") { opt_options.labelVisible = true; } if (typeof opt_options.raiseOnDrag === "undefined") { opt_options.raiseOnDrag = true; } if (typeof opt_options.clickable === "undefined") { opt_options.clickable = true; } if (typeof opt_options.draggable === "undefined") { opt_options.draggable = false; } if (typeof opt_options.optimized === "undefined") { opt_options.optimized = false; } opt_options.crossImage = opt_options.crossImage || "http" + (document.location.protocol === "https:" ? "s" : "") + "://maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png"; opt_options.handCursor = opt_options.handCursor || "http" + (document.location.protocol === "https:" ? "s" : "") + "://maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur"; opt_options.optimized = false; // Optimized rendering is not supported ////////////////////////////////////////////////////////// // New ////////////////////////////////////////////////////////// this.label = new MarkerLabel_(opt_options.id, this, opt_options.crossImage, opt_options.handCursor); // Bind the label to the marker // Call the parent constructor. It calls Marker.setValues to initialize, so all // the new parameters are conveniently saved and can be accessed with get/set. // Marker.set triggers a property changed event (called "propertyname_changed") // that the marker label listens for in order to react to state changes. google.maps.Marker.apply(this, arguments); } inherits(MarkerWithLabel, google.maps.Marker); /** * Overrides the standard Marker setMap function. * #param {Map} theMap The map to which the marker is to be added. * #private */ MarkerWithLabel.prototype.setMap = function (theMap) { // Call the inherited function... google.maps.Marker.prototype.setMap.apply(this, arguments); // ... then deal with the label: this.label.setMap(theMap); }; If you would like to compress/minify/uglify the code, I recommend https://javascript-minifier.com/
EaselJS BitmapAnimation.gotoAndPlay lag
I've written a requirejs module to take a BitmapAnimation and add methods to allow it to move around on the stage at a certain speed and once its destination has been reached, to move to another looped animation. The problem I'm having is that there is quite a lot of lag between gotoAndPlay being called and anything happening on stage. I've console.logged the methods that call gotoAndPlay but the logs happen immediately where the animation doesn't change as quickly. here is my abstract object: 'use strict'; /** * AbstractCharacter * Provides common functionality for all characters */ define(['easeljs'], function () { var AbstractCharacter = function (model) { this.model = model; // changed from original code // allows better encapsulation this._s = new createjs.SpriteSheet(model.ssInit); // add reversed frames createjs.SpriteSheetUtils.addFlippedFrames(this._s, true, false, false), this.initialize(); }; AbstractCharacter.prototype = Object.create(createjs.BitmapAnimation.prototype); AbstractCharacter.prototype.constructor = AbstractCharacter; // speed constant AbstractCharacter.prototype.SPEED = 10; // from Flash CS6 AbstractCharacter.prototype.BitmapAnimation_initialize = AbstractCharacter.prototype.initialize; /** * moves a character to a destination and sets their state at the end of the move * #param {object} destination destination coordinates {x:XX} * #param {string} nextAction the next action to perform * #return {null} */ AbstractCharacter.prototype.moveTo = function(destination, nextAction) { nextAction = nextAction || 'idle'; if (destination) { if (destination.x < this.x) { this.walkLeft(); } else { this.walkRight(); } this.destination = destination; this.nextAction = nextAction; } }; /** * public method to perform action * #param {string} room name of the current room the character is in * #param {string} action action name * #param {object} params any parameters required by the action * #return {null} */ AbstractCharacter.prototype.action = function(room, action, params) { switch (action) { case 'walk' : this.moveTo(params, 'idle'); break; case 'talk' : this.moveTo(params, 'talk'); break; case 'sleep' : this.moveTo(this.model.actions.room[room].sleep, 'sleep'); break; } }; /** * onTick callback to call next animation frame for use with easeljs * #return {null} */ AbstractCharacter.prototype.onTick = function() { if (this.destination) { var destination = this.destination, speed; if (destination.x < this.x) { speed = -this.SPEED; if (this.x + speed < destination.x) { this.endMove(destination); } else { this.x += speed; } } else { speed = this.SPEED; if (this.x + speed > destination.x) { this.endMove(destination); } else { this.x += speed; } } } }; /** * finishes move by calling nextAction() and clearing away desination and nextAction vars * #param {object} destination format {x:XX} * #return {null} */ AbstractCharacter.prototype.endMove = function(destination) { this.x = destination.x; this[this.nextAction](); this.destination = null; this.nextAction = null; }; /** * these methods come from Flash CS6 * the only difference is that I've removed * the reference to the Object prototype */ AbstractCharacter.prototype.initialize = function() { this.BitmapAnimation_initialize(this._s); this.idle(); }; AbstractCharacter.prototype.idle = function(){ console.log('idle'); // fires almost a second before animation plays this.gotoAndPlay("idle"); }; AbstractCharacter.prototype.idleLeft = function(){ this.gotoAndPlay("idleside"); }; AbstractCharacter.prototype.idleRight = function(){ this.gotoAndPlay("idleside_h"); }; AbstractCharacter.prototype.walkLeft = function(){ this.gotoAndPlay("walk"); }; AbstractCharacter.prototype.walkRight = function(){ this.gotoAndPlay("walk_h"); }; AbstractCharacter.prototype.talk = function(){ console.log('talk'); this.gotoAndPlay("talk"); }; AbstractCharacter.prototype.sleep = function(){ console.log('sleep'); // fires almost a second before animation plays this.gotoAndPlay("sleep"); }; // public interface return AbstractCharacter; }); EDIT: this only seems to affect chrome - ie9 and firefox17 are fine EDIT: if I use the following code instead of extending my abstractcharacter, this plays out without issue or lag var test = new createjs.BitmapAnimation(new createjs.SpriteSheet({ images: [model.assets['mycharacters']], frames: [[0,0,129,344,0,74,327],[129,0,129,345,0,74,327],[258,0,162,331,0,93,313],[420,0,162,331,0,93,313],[582,0,141,338,0,91,320],[723,0,141,338,0,91,320],[864,0,129,345,0,74,327],[993,0,129,345,0,74,327],[1122,0,162,331,0,93,313],[1284,0,162,331,0,93,313],[1446,0,141,338,0,91,320],[1446,0,141,338,0,91,320],[1587,0,129,344,0,74,327],[1716,0,129,344,0,74,327],[1845,0,162,330,0,93,313],[0,345,162,330,0,93,313],[162,345,141,337,0,91,320],[303,345,141,337,0,91,320],[444,345,129,344,0,74,327],[573,345,129,344,0,74,327],[702,345,162,330,0,93,313],[864,345,162,330,0,93,313],[1026,345,141,337,0,91,320],[1167,345,141,337,0,91,320],[1308,345,129,344,0,74,327],[1437,345,129,344,0,74,327],[1566,345,162,330,0,93,313],[1728,345,162,331,0,93,313],[1890,345,141,338,0,91,320],[0,689,141,338,0,91,320],[141,689,129,345,0,74,327],[270,689,129,345,0,74,327],[399,689,162,331,0,93,313],[399,689,162,331,0,93,313],[561,689,141,338,0,91,320],[702,689,141,338,0,91,320],[0,0,129,344,0,74,327],[843,689,129,343,0,74,327],[972,689,162,330,0,93,313],[1134,689,162,330,0,93,313],[1296,689,141,337,0,91,320],[1437,689,141,337,0,91,320],[1578,689,129,344,0,74,327],[1707,689,129,344,0,74,327],[1836,689,162,330,0,93,313],[0,1034,162,329,0,93,313],[162,1034,141,336,0,91,320],[303,1034,141,336,0,91,320],[444,1034,129,343,0,74,327],[573,1034,129,343,0,74,327],[702,1034,162,329,0,93,313],[864,1034,162,329,0,93,313],[1026,1034,141,335,0,91,320],[1167,1034,141,336,0,91,320],[1308,1034,129,343,0,74,327],[1308,1034,129,343,0,74,327],[1437,1034,162,329,0,93,313],[1599,1034,162,329,0,93,313],[1761,1034,141,336,0,91,320],[1902,1034,141,336,0,91,320],[0,1377,129,343,0,74,327],[129,1377,129,343,0,74,327],[258,1377,162,329,0,93,313],[420,1377,162,329,0,93,313],[582,1377,141,336,0,91,320],[723,1377,141,336,0,91,320],[864,1377,129,343,0,74,327],[993,1377,129,343,0,74,327],[1122,1377,162,330,0,93,313],[1284,1377,162,330,0,93,313],[1446,1377,141,337,0,91,320],[1587,1377,141,337,0,91,320],[1728,1377,129,344,0,74,327],[1857,1377,129,344,0,74,327],[0,1721,162,330,0,93,313],[162,1721,162,330,0,93,313],[324,1721,141,325,0,91,320],[465,1721,158,337,0,91,320],[623,1721,141,345,0,74,327],[764,1721,141,345,0,74,327],[905,1721,162,331,0,93,313],[1067,1721,162,331,0,93,313],[1229,1721,158,338,0,91,320],[1387,1721,158,338,0,91,320],[1545,1721,141,345,0,74,327],[1686,1721,141,344,0,74,327],[1827,1721,162,330,0,93,313],[0,2066,162,330,0,93,313],[162,2066,158,337,0,91,320],[320,2066,158,337,0,91,320],[478,2066,141,344,0,74,327],[619,2066,141,344,0,74,327],[760,2066,162,330,0,93,313],[922,2066,162,330,0,93,313],[1084,2066,158,337,0,91,320],[1242,2066,158,337,0,91,320],[1400,2066,141,344,0,74,327],[1541,2066,141,345,0,74,327],[1682,2066,162,331,0,93,313],[1844,2066,162,331,0,93,313],[0,2411,158,338,0,91,320],[158,2411,158,338,0,91,320],[316,2411,141,345,0,74,327],[457,2411,141,345,0,74,327],[598,2411,162,331,0,93,313],[760,2411,162,319,0,93,313],[922,2411,141,337,0,91,320],[1063,2411,141,338,0,91,320],[1204,2411,129,345,0,74,327],[1333,2411,129,345,0,74,327],[1462,2411,162,331,0,93,313],[1624,2411,162,331,0,93,313],[1786,2411,141,338,0,91,320],[0,2756,141,338,0,91,320],[141,2756,129,345,0,74,327],[270,2756,129,345,0,74,327],[399,2756,162,331,0,93,313],[561,2756,162,330,0,93,313],[723,2756,141,337,0,91,320],[864,2756,141,337,0,91,320],[1005,2756,129,344,0,74,327],[1134,2756,129,344,0,74,327],[1263,2756,162,330,0,93,313],[1425,2756,162,330,0,93,313],[1587,2756,141,337,0,91,320],[1728,2756,141,337,0,91,320],[1869,2756,129,344,0,74,327],[0,3101,129,344,0,74,327],[129,3101,162,330,0,93,313],[291,3101,162,330,0,93,313],[453,3101,141,337,0,91,320],[594,3101,141,337,0,91,320],[735,3101,129,344,0,74,327],[864,3101,129,345,0,74,327],[993,3101,162,331,0,93,313],[1155,3101,162,331,0,93,313],[1317,3101,141,338,0,91,320],[1458,3101,141,338,0,91,320],[1599,3101,129,345,0,74,327],[1728,3101,129,345,0,74,327],[1857,3101,162,331,0,93,313],[0,3446,162,331,0,93,313],[162,3446,141,326,0,91,320],[303,3446,148,280,0,82,281]], animations: {idle:[0,36, true], idleside:[37,76, true], walk:[77,105, true], talk:[106,142, true], sleep:[143,143, true]} })); this.addChild(test); test.gotoAndPlay('idle'); setTimeout(function () { console.log('idleside'); test.gotoAndPlay('idleside'); }, 3000); setTimeout(function () { console.log('walk'); test.gotoAndPlay('walk'); },6000) setTimeout(function () { console.log('talk'); test.gotoAndPlay('talk'); },9000) setTimeout(function () { console.log('sleep'); test.gotoAndPlay('sleep'); },12000)
I've discovered what the issue is: // add reversed frames createjs.SpriteSheetUtils.addFlippedFrames(this._s, true, false, false), The issue is that the addReversedFrames has issues in chrome - this may be caused by the size of my spritesheet - I'm going to test what happens when i break the spritesheet down into multiple parts This may be because of the issue documented with multiple images here: Stuttering animation when spanning multiple sprite sheets in easeljs
I've updated my abstract class to use scaleX = -1 instead of flipped frames which works much better. Note: there will be a slight cpu overhead to do this but testing will bear this out. Worse case scenario, I'll have to add the extra frames to the assets. enjoy 'use strict'; /** * AbstractCharacter * Provides common functionality for all characters * #author Allandt Bik-Elliott * #version 0.1 */ define(['easeljs'], function () { var AbstractCharacter = function (model) { this.model = model; // changed from original code // allows better encapsulation this._s = new createjs.SpriteSheet(model.ssInit); // add reversed frames // NOTE: removed because there is lag switching between spritesheets //createjs.SpriteSheetUtils.addFlippedFrames(this._s, true, false, false), this.initialize(); }; AbstractCharacter.prototype = Object.create(createjs.BitmapAnimation.prototype); AbstractCharacter.prototype.constructor = AbstractCharacter; // speed constant AbstractCharacter.prototype.SPEED = 10; // from Flash CS6 AbstractCharacter.prototype.BitmapAnimation_initialize = AbstractCharacter.prototype.initialize; /** * moves a character to a destination and sets their state at the end of the move * #param {object} destination destination coordinates {x:XX} * #param {string} nextAction the next action to perform * #return {null} */ AbstractCharacter.prototype.moveTo = function(destination, nextAction) { nextAction = nextAction || 'idle'; if (destination) { if (destination.x < this.x) { this.walkLeft(); } else { this.walkRight(); } this.model.destination = destination; this.model.nextAction = nextAction; } }; /** * public method to perform action * #param {string} room name of the current room the character is in * #param {string} action action name * #param {object} params any parameters required by the action * #return {null} */ AbstractCharacter.prototype.act = function(room, action, actionparams) { //console.log(action); switch (action) { case 'walk' : this.moveTo(actionparams, 'idle'); break; case 'talk' : this.moveTo(actionparams, 'talk'); break; case 'sleep' : this.moveTo(this.model.actions.room[room].sleep, 'sleep'); break; } }; /** * onTick callback for use with easeljs * #return {null} */ AbstractCharacter.prototype.onTick = function() { if (this.model.destination) { var destination = this.model.destination, speed; if (destination.x < this.x) { speed = -this.SPEED; if (this.x + speed < destination.x) { this.endMove(destination); } else { this.x += speed; } } else { speed = this.SPEED; if (this.x + speed > destination.x) { this.endMove(destination); } else { this.x += speed; } } } }; /** * finishes move by calling nextAction() and clearing away desination and nextAction vars * #param {object} destination format {x:XX} * #return {null} */ AbstractCharacter.prototype.endMove = function(destination) { //console.log('ending move'); this.x = destination.x; this[this.model.nextAction](); this.model.destination = null; this.model.nextAction = null; }; /** * these methods come from Flash CS6 * the only difference is that I've removed * the reference to the Object prototype * * scaleX used instead of flipped frames */ AbstractCharacter.prototype.initialize = function() { this.BitmapAnimation_initialize(this._s); this.idle(); }; AbstractCharacter.prototype.idle = function(){ //console.log('idle'); this.gotoAndPlay("idle"); this.scaleX = 1; }; AbstractCharacter.prototype.idleLeft = function(){ //console.log('idle left'); this.gotoAndPlay("idleside"); this.scaleX = 1; }; AbstractCharacter.prototype.idleRight = function(){ //console.log('idle right'); // this.gotoAndPlay("idleside_h"); this.gotoAndPlay("idleside"); this.scaleX = -1; }; AbstractCharacter.prototype.walkLeft = function(){ //console.log('walk left'); this.gotoAndPlay("walk"); this.scaleX = 1; }; AbstractCharacter.prototype.walkRight = function(){ //console.log('walk right'); // this.gotoAndPlay("walk_h"); this.gotoAndPlay("walk"); this.scaleX = -1; }; AbstractCharacter.prototype.talk = function(){ //console.log('talk'); this.gotoAndPlay("talk"); this.scaleX = 1; }; AbstractCharacter.prototype.sleep = function(){ //console.log('sleep'); this.gotoAndPlay("sleep"); this.scaleX = 1; }; // public interface return AbstractCharacter; });
How to animate zoom on jquery iviewer plugin?
I have this jquery plugin that zooms in on photos, but I would like to animate the zoom so it just doesn't zoom in, but so that there is like an animated effect on the zoom. Is this possible? Here is the page I have the plugin set up on right now. http://www.buildinfocus.com/clients/gallery_detail.php?title=1&mapid=239 Maybe something like this http://api.jquery.com/animate/ Below is the code I have for plugin (function($){ $.fn.iviewer = function(o) { return this.each(function() { $(this).data('viewer', new $iv(this,o)); }); } var defaults = { /** * start zoom value for image, not used now * may be equal to "fit" to fit image into container or scale in % **/ zoom: "fit", /** * base value to scale image **/ zoom_base: 100, /** * maximum zoom **/ zoom_max: 800, /** * minimum zoom **/ zoom_min: 25, /** * base of rate multiplier. * zoom is calculated by formula: zoom_base * zoom_delta^rate **/ zoom_delta: 1.4, /** * if true plugin doesn't add its own controls **/ ui_disabled: false, /** * if false, plugin doesn't bind resize event on window and this must * be handled manually **/ update_on_resize: true, /** * event is triggered when zoom value is changed * #param int new zoom value * #return boolean if false zoom action is aborted **/ onZoom: null, /** * callback is fired after plugin setup **/ initCallback: null, /** * event is fired on drag begin * #param object coords mouse coordinates on the image * #return boolean if false is returned, drag action is aborted **/ onStartDrag: null, /** * event is fired on drag action * #param object coords mouse coordinates on the image **/ onDrag: null, /** * event is fired when mouse moves over image * #param object coords mouse coordinates on the image **/ onMouseMove: null, /** * mouse click event * #param object coords mouse coordinates on the image **/ onClick: null, /** * event is fired when image starts to load */ onStartLoad: null, /** * event is fired, when image is loaded and initially positioned */ onFinishLoad: null }; $.iviewer = function(e,o) { var me = this; /* object containing actual information about image * #img_object.object - jquery img object * #img_object.orig_{width|height} - original dimensions * #img_object.display_{width|height} - actual dimensions */ this.img_object = {}; this.zoom_object = {}; //object to show zoom status this.image_loaded = false; //drag variables this.dx = 0; this.dy = 0; this.dragged = false; this.settings = $.extend({}, defaults, o || {}); this.current_zoom = this.settings.zoom; if(this.settings.src === null){ return; } this.container = $(e); this.update_container_info(); //init container this.container.css("overflow","hidden"); if(this.settings.update_on_resize == true) { $(window).resize(function() { me.update_container_info(); }); } this.img_object.x = 0; this.img_object.y = 0; //init object this.img_object.object = $("<img>"). css({ position: "absolute", top :"0px", left: "0px"}). //this is needed, because chromium sets them auto otherwise //bind mouse events mousedown(function(e){ return me.drag_start(e); }). mousemove(function(e){return me.drag(e)}). mouseup(function(e){return me.drag_end(e)}). click(function(e){return me.click(e)}). mouseleave(function(e){return me.drag_end(e)}). mousewheel(function(ev, delta) { //this event is there instead of containing div, because //at opera it triggers many times on div var zoom = (delta > 0)?1:-1; me.zoom_by(zoom); return false; }); this.img_object.object.prependTo(me.container); this.loadImage(this.settings.src); if(!this.settings.ui_disabled) { this.createui(); } if(this.settings.initCallback) { this.settings.initCallback.call(this); } } var $iv = $.iviewer; $iv.fn = $iv.prototype = { iviewer : "0.4.2" } $iv.fn.extend = $iv.extend = $.extend; $iv.fn.extend({ loadImage: function(src) { this.current_zoom = this.settings.zoom; this.image_loaded = false; var me = this; if(this.settings.onStartLoad) { this.settings.onStartLoad.call(this); } this.img_object.object.unbind('load'). removeAttr("src"). removeAttr("width"). removeAttr("height"). css({ top: 0, left: 0 }). load(function(){ me.image_loaded = true; me.img_object.display_width = me.img_object.orig_width = this.width; me.img_object.display_height = me.img_object.orig_height = this.height; if(!me.container.hasClass("iviewer_cursor")){ me.container.addClass("iviewer_cursor"); } if(me.settings.zoom == "fit"){ me.fit(); } else { me.set_zoom(me.settings.zoom); } if(me.settings.onFinishLoad) { me.settings.onFinishLoad.call(me); } //src attribute is after setting load event, or it won't work }).attr("src",src); }, /** * fits image in the container **/ fit: function() { var aspect_ratio = this.img_object.orig_width / this.img_object.orig_height; var window_ratio = this.settings.width / this.settings.height; var choose_left = (aspect_ratio > window_ratio); var new_zoom = 0; if(choose_left){ new_zoom = this.settings.width / this.img_object.orig_width * 100; } else { new_zoom = this.settings.height / this.img_object.orig_height * 100; } this.set_zoom(new_zoom); }, /** * center image in container **/ center: function() { this.setCoords(-Math.round((this.img_object.display_height - this.settings.height)/2), -Math.round((this.img_object.display_width - this.settings.width)/2)); }, /** * move a point in container to the center of display area * #param x a point in container * #param y a point in container **/ moveTo: function(x, y) { var dx = x-Math.round(this.settings.width/2); var dy = y-Math.round(this.settings.height/2); var new_x = this.img_object.x - this.dx; var new_y = this.img_object.y - this.dy; this.setCoords(new_x, new_y); }, /** * set coordinates of upper left corner of image object **/ setCoords: function(x,y) { //do nothing while image is being loaded if(!this.image_loaded) { return; } //check new coordinates to be correct (to be in rect) if(y > 0){ y = 0; } if(x > 0){ x = 0; } if(y + this.img_object.display_height < this.settings.height){ y = this.settings.height - this.img_object.display_height; } if(x + this.img_object.display_width < this.settings.width){ x = this.settings.width - this.img_object.display_width; } if(this.img_object.display_width <= this.settings.width){ x = -(this.img_object.display_width - this.settings.width)/2; } if(this.img_object.display_height <= this.settings.height){ y = -(this.img_object.display_height - this.settings.height)/2; } this.img_object.x = x; this.img_object.y = y; this.img_object.object.css("top",y + "px") .css("left",x + "px"); }, /** * convert coordinates on the container to the coordinates on the image (in original size) * * #return object with fields x,y according to coordinates or false * if initial coords are not inside image **/ containerToImage : function (x,y) { if(x < this.img_object.x || y < this.img_object.y || x > this.img_object.x + this.img_object.display_width || y > this.img_object.y + this.img_object.display_height) { return false; } return { x : $iv.descaleValue(x - this.img_object.x, this.current_zoom), y : $iv.descaleValue(y - this.img_object.y, this.current_zoom) }; }, /** * convert coordinates on the image (in original size) to the coordinates on the container * * #return object with fields x,y according to coordinates or false * if initial coords are not inside image **/ imageToContainer : function (x,y) { if(x > this.img_object.orig_width || y > this.img_object.orig_height) { return false; } return { x : this.img_object.x + $iv.scaleValue(x, this.current_zoom), y : this.img_object.y + $iv.scaleValue(y, this.current_zoom) }; }, /** * get mouse coordinates on the image * #param e - object containing pageX and pageY fields, e.g. mouse event object * * #return object with fields x,y according to coordinates or false * if initial coords are not inside image **/ getMouseCoords : function(e) { var img_offset = this.img_object.object.offset(); return { x : $iv.descaleValue(e.pageX - img_offset.left, this.current_zoom), y : $iv.descaleValue(e.pageY - img_offset.top, this.current_zoom) }; }, /** * set image scale to the new_zoom * #param new_zoom image scale in % **/ set_zoom: function(new_zoom) { if(this.settings.onZoom && this.settings.onZoom.call(this, new_zoom) == false) { return; } //do nothing while image is being loaded if(!this.image_loaded) { return; } if(new_zoom < this.settings.zoom_min) { new_zoom = this.settings.zoom_min; } else if(new_zoom > this.settings.zoom_max) { new_zoom = this.settings.zoom_max; } /* we fake these values to make fit zoom properly work */ if(this.current_zoom == "fit") { var old_x = Math.round(this.settings.width/2 + this.img_object.orig_width/2); var old_y = Math.round(this.settings.height/2 + this.img_object.orig_height/2); this.current_zoom = 100; } else { var old_x = -parseInt(this.img_object.object.css("left"),10) + Math.round(this.settings.width/2); var old_y = -parseInt(this.img_object.object.css("top"),10) + Math.round(this.settings.height/2); } var new_width = $iv.scaleValue(this.img_object.orig_width, new_zoom); var new_height = $iv.scaleValue(this.img_object.orig_height, new_zoom); var new_x = $iv.scaleValue( $iv.descaleValue(old_x, this.current_zoom), new_zoom); var new_y = $iv.scaleValue( $iv.descaleValue(old_y, this.current_zoom), new_zoom); new_x = this.settings.width/2 - new_x; new_y = this.settings.height/2 - new_y; this.img_object.object.attr("width",new_width) .attr("height",new_height); this.img_object.display_width = new_width; this.img_object.display_height = new_height; this.setCoords(new_x, new_y); this.current_zoom = new_zoom; $.isFunction( this.settings.onAfterZoom ) && this.settings.onAfterZoom.call( this, new_zoom ); this.update_status(); }, /** * changes zoom scale by delta * zoom is calculated by formula: zoom_base * zoom_delta^rate * #param Integer delta number to add to the current multiplier rate number **/ zoom_by: function(delta) { var closest_rate = this.find_closest_zoom_rate(this.current_zoom); var next_rate = closest_rate + delta; var next_zoom = this.settings.zoom_base * Math.pow(this.settings.zoom_delta, next_rate) if(delta > 0 && next_zoom < this.current_zoom) { next_zoom *= this.settings.zoom_delta; } if(delta < 0 && next_zoom > this.current_zoom) { next_zoom /= this.settings.zoom_delta; } this.set_zoom(next_zoom); }, /** * finds closest multiplier rate for value * basing on zoom_base and zoom_delta values from settings * #param Number value zoom value to examine **/ find_closest_zoom_rate: function(value) { if(value == this.settings.zoom_base) { return 0; } function div(val1,val2) { return val1 / val2 }; function mul(val1,val2) { return val1 * val2 }; var func = (value > this.settings.zoom_base)?mul:div; var sgn = (value > this.settings.zoom_base)?1:-1; var mltplr = this.settings.zoom_delta; var rate = 1; while(Math.abs(func(this.settings.zoom_base, Math.pow(mltplr,rate)) - value) > Math.abs(func(this.settings.zoom_base, Math.pow(mltplr,rate+1)) - value)) { rate++; } return sgn * rate; }, /* update scale info in the container */ update_status: function() { if(!this.settings.ui_disabled) { var percent = Math.round(100*this.img_object.display_height/this.img_object.orig_height); if(percent) { this.zoom_object.html(percent + "%"); } } }, update_container_info: function() { this.settings.height = this.container.height(); this.settings.width = this.container.width(); }, /** * callback for handling mousdown event to start dragging image **/ drag_start: function(e) { if(this.settings.onStartDrag && this.settings.onStartDrag.call(this,this.getMouseCoords(e)) == false) { return false; } /* start drag event*/ this.dragged = true; this.container.addClass("iviewer_drag_cursor"); this.dx = e.pageX - this.img_object.x; this.dy = e.pageY - this.img_object.y; return false; }, /** * callback for handling mousmove event to drag image **/ drag: function(e) { this.settings.onMouseMove && this.settings.onMouseMove.call(this,this.getMouseCoords(e)); if(this.dragged){ this.settings.onDrag && this.settings.onDrag.call(this,this.getMouseCoords(e)); var ltop = e.pageY -this.dy; var lleft = e.pageX -this.dx; this.setCoords(lleft, ltop); return false; } }, /** * callback for handling stop drag **/ drag_end: function(e) { this.container.removeClass("iviewer_drag_cursor"); this.dragged=false; }, click: function(e) { this.settings.onClick && this.settings.onClick.call(this,this.getMouseCoords(e)); }, /** * create zoom buttons info box **/ createui: function() { var me=this; $("<div>").addClass("iviewer_zoom_in").addClass("iviewer_common"). addClass("iviewer_button"). mousedown(function(){me.zoom_by(1); return false;}).appendTo(this.container); $("<div>").addClass("iviewer_zoom_out").addClass("iviewer_common"). addClass("iviewer_button"). mousedown(function(){me.zoom_by(- 1); return false;}).appendTo(this.container); $("<div>").addClass("iviewer_zoom_zero").addClass("iviewer_common"). addClass("iviewer_button"). mousedown(function(){me.set_zoom(100); return false;}).appendTo(this.container); $("<div>").addClass("iviewer_zoom_fit").addClass("iviewer_common"). addClass("iviewer_button"). mousedown(function(){me.fit(this); return false;}).appendTo(this.container); this.zoom_object = $("<div>").addClass("iviewer_zoom_status").addClass("iviewer_common"). appendTo(this.container); this.update_status(); //initial status update } }); $iv.extend({ scaleValue: function(value, toZoom) { return value * toZoom / 100; }, descaleValue: function(value, fromZoom) { return value * 100 / fromZoom; } }); })(jQuery);
Try updating to the latest version, it looks to support smooth zoom transition (so you won't have to implement this yourself): https://github.com/can3p/iviewer/blob/master/jquery.iviewer.js#L415 On line 415, the set_zoom function animates the trnasition, as opposed to the older code you pasted, which just changes the css values.
The last version of the plugin(0.5) supports smooth zoom, try it.