I'm currently working on a javascript animation that transitions details on an event action - however, I am having an issue removing a certain rogue div.
I have my init function as follows:
var demo = (function(window, undefined) {
function init() {
_mapPolygons(pattern);
_bindCards();
$(document.getElementsByClassName("card__overlay")).addClass(CLASSES.overlayHidden);
};
return {
init: init
};
})(window);
window.onload = demo.init;
The full snippet is here:
'use strict';
/**
* Demo.
*/
var demo = (function(window, undefined) {
/**
* Enum of CSS selectors.
*/
var SELECTORS = {
pattern: '.pattern',
card: '.card',
cardImage: '.card__image',
cardClose: '.card__btn-close',
};
/**
* Enum of CSS classes.
*/
var CLASSES = {
patternHidden: 'pattern--hidden',
polygon: 'polygon',
polygonHidden: 'polygon--hidden',
overlayHidden: 'overlay--hidden',
};
/**
* Map of svg paths and points.
*/
var polygonMap = {
paths: null,
points: null
};
/**
* Container of Card instances.
*/
var layout = {};
/**
* Initialise demo.
*/
function init() {
// For options see: https://github.com/qrohlf/Trianglify
var pattern = Trianglify({
width: window.innerWidth,
height: window.innerHeight,
cell_size: 90,
variance: 1,
stroke_width: 0.6,
color_function : function(x, y) {
return '#f0f3f5';
}
}).svg(); // Render as SVG.
_mapPolygons(pattern);
_bindCards();
$(document.getElementsByClassName("card__overlay")).addClass(CLASSES.overlayHidden);
};
/**
* Store path elements, map coordinates and sizes.
* #param {Element} pattern The SVG Element generated with Trianglify.
* #private
*/
function _mapPolygons(pattern) {
// Append SVG to pattern container.
$(SELECTORS.pattern).append(pattern);
// Convert nodelist to array,
// Used `.childNodes` because IE doesn't support `.children` on SVG.
polygonMap.paths = [].slice.call(pattern.childNodes);
polygonMap.points = [];
polygonMap.paths.forEach(function(polygon) {
// Hide polygons by adding CSS classes to each svg path (used attrs because of IE).
$(polygon).attr('class', CLASSES.polygon + ' ' + CLASSES.polygonHidden);
var rect = polygon.getBoundingClientRect();
var point = {
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2
};
polygonMap.points.push(point);
});
// All polygons are hidden now, display the pattern container.
$(SELECTORS.pattern).removeClass(CLASSES.patternHidden);
};
/**
* Bind Card elements.
* #private
*/
function _bindCards() {
var elements = $(SELECTORS.card);
$.each(elements, function(card, i) {
var instance = new Card(i, card);
layout[i] = {
card: instance
};
var cardImage = $(card).find(SELECTORS.cardImage);
var cardClose = $(card).find(SELECTORS.cardClose);
$(cardImage).on('click', _playSequence.bind(this, true, i));
$(cardClose).on('click', _playSequence.bind(this, false, i));
});
};
/**
* Create a sequence for the open or close animation and play.
* #param {boolean} isOpenClick Flag to detect when it's a click to open.
* #param {number} id The id of the clicked card.
* #param {Event} e The event object.
* #private
*
*/
function _playSequence(isOpenClick, id, e) {
var card = layout[id].card;
// Prevent when card already open and user click on image.
if (card.isOpen && isOpenClick) return;
// Create timeline for the whole sequence.
var sequence = new TimelineLite({paused: true});
var tweenOtherCards = _showHideOtherCards(id);
if (!card.isOpen) {
// Open sequence.
sequence.add(tweenOtherCards);
sequence.add(card.openCard(_onCardMove), 0);
} else {
// Close sequence.
var closeCard = card.closeCard();
var position = closeCard.duration() * 0.8; // 80% of close card tween.
sequence.add(closeCard);
sequence.add(tweenOtherCards, position);
}
sequence.play();
};
/**
* Show/Hide all other cards.
* #param {number} id The id of the clcked card to be avoided.
* #private
*/
function _showHideOtherCards(id) {
var TL = new TimelineLite;
var selectedCard = layout[id].card;
for (var i in layout) {
var card = layout[i].card;
// When called with `openCard`.
if (card.id !== id && !selectedCard.isOpen) {
TL.add(card.hideCard(), 0);
}
// When called with `closeCard`.
if (card.id !== id && selectedCard.isOpen) {
TL.add(card.showCard(), 0);
}
}
return TL;
};
/**
* Callback to be executed on Tween update, whatever a polygon
* falls into a circular area defined by the card width the path's
* CSS class will change accordingly.
* #param {Object} track The card sizes and position during the floating.
* #private
*/
function _onCardMove(track) {
var radius = track.width / 2;
var center = {
x: track.x,
y: track.y
};
polygonMap.points.forEach(function(point, i) {
if (_detectPointInCircle(point, radius, center)) {
$(polygonMap.paths[i]).attr('class', CLASSES.polygon);
} else {
$(polygonMap.paths[i]).attr('class', CLASSES.polygon + ' ' + CLASSES.polygonHidden);
}
});
}
/**
* Detect if a point is inside a circle area.
* #private
*/
function _detectPointInCircle(point, radius, center) {
var xp = point.x;
var yp = point.y;
var xc = center.x;
var yc = center.y;
var d = radius * radius;
var isInside = Math.pow(xp - xc, 2) + Math.pow(yp - yc, 2) <= d;
return isInside;
};
// Expose methods.
return {
init: init
};
})(window);
// Kickstart Demo.
window.onload = demo.init;
)
I'm essentially adding a class which should completely hide a div element, for which I have overkilled due in part to major frustrations:
.overlay--hidden {
display: none !important;
overflow: hidden !important;
opacity: 0 !important;
}
So this should completely kill the card__overlay div. I have added the same css "stylings" within the inspector and it works as should.
<div class="card__content card__overlay">
<div class="card__caption">
<p class="card__subtitle overlay">NEWS</p>
</div>
</div>
My issue is that essentially the getElementsByClassName method in the init function doesn't seem to be working - the class I want which is essentially hiding the div is not being added!
Related
I use this code bellow for signature input, the signature pad working fine with web browser but not working means not draw on mobile browser.
I use this code bellow for signature input, the signature pad working fine with web browser but not working means not draw on mobile browser.
<script type="text/javascript">
/*! http://keith-wood.name/signature.html
Signature plugin for jQuery UI v1.2.1.
Requires excanvas.js in IE.
Written by Keith Wood (wood.keith{at}optusnet.com.au) April 2012.
Available under the MIT (http://keith-wood.name/licence.html) license.
Please attribute the author if you use it. */
/* globals G_vmlCanvasManager */
(function($) { // Hide scope, no $ conflict
'use strict';
var signatureOverrides = {
options: {
distance: 0,
background: '#fff',
color: '#000',
thickness: 2,
guideline: false,
guidelineColor: '#a0a0a0',
guidelineOffset: 50,
guidelineIndent: 10,
notAvailable: 'Your browser doesn\'t support signing',
scale: 1,
syncField: null,
syncFormat: 'JSON',
svgStyles: false,
change: null
},
/** Initialise a new signature area.
#memberof Signature
#private */
_create: function() {
this.element.addClass(this.widgetFullName || this.widgetBaseClass);
try {
this.canvas = $('<canvas width="' + this.element.width() + '" height="' +
this.element.height() + '">' + this.options.notAvailable + '</canvas>')[0];
this.element.append(this.canvas);
}
catch (e) {
$(this.canvas).remove();
this.resize = true;
this.canvas = document.createElement('canvas');
this.canvas.setAttribute('width', this.element.width());
this.canvas.setAttribute('height', this.element.height());
this.canvas.innerHTML = this.options.notAvailable;
this.element.append(this.canvas);
/* jshint -W106 */
if (G_vmlCanvasManager) { // Requires excanvas.js
G_vmlCanvasManager.initElement(this.canvas);
}
/* jshint +W106 */
}
this.ctx = this.canvas.getContext('2d');
this._refresh(true);
this._mouseInit();
},
/** Refresh the appearance of the signature area.
#memberof Signature
#private
#param {boolean} init <code>true</code> if initialising. */
_refresh: function(init) {
if (this.resize) {
var parent = $(this.canvas);
$('div', this.canvas).css({width: parent.width(), height: parent.height()});
}
this.ctx.fillStyle = this.options.background;
this.ctx.strokeStyle = this.options.color;
this.ctx.lineWidth = this.options.thickness;
this.ctx.lineCap = 'round';
this.ctx.lineJoin = 'round';
this.clear(init);
},
/** Clear the signature area.
#memberof Signature
#param {boolean} init <code>true</code> if initialising - internal use only.
#example $(selector).signature('clear') */
clear: function(init) {
if (this.options.disabled) {
return;
}
this.ctx.clearRect(0, 0, this.element.width(), this.element.height());
this.ctx.fillRect(0, 0, this.element.width(), this.element.height());
if (this.options.guideline) {
this.ctx.save();
this.ctx.strokeStyle = this.options.guidelineColor;
this.ctx.lineWidth = 1;
this.ctx.beginPath();
this.ctx.moveTo(this.options.guidelineIndent,
this.element.height() - this.options.guidelineOffset);
this.ctx.lineTo(this.element.width() - this.options.guidelineIndent,
this.element.height() - this.options.guidelineOffset);
this.ctx.stroke();
this.ctx.restore();
}
this.lines = [];
if (!init) {
this._changed();
}
},
/** Synchronise changes and trigger a change event.
#memberof Signature
#private
#param {Event} event The triggering event. */
_changed: function(event) {
if (this.options.syncField) {
var output = '';
switch (this.options.syncFormat) {
case 'PNG':
output = this.toDataURL();
break;
case 'JPEG':
output = this.toDataURL('image/jpeg');
break;
case 'SVG':
output = this.toSVG();
break;
default:
output = this.toJSON();
}
$(this.options.syncField).val(output);
}
this._trigger('change', event, {});
},
/** Refresh the signature when options change.
#memberof Signature
#private
#param {object} options The new option values. */
_setOptions: function(/* options */) {
if (this._superApply) {
this._superApply(arguments); // Base widget handling
}
else {
$.Widget.prototype._setOptions.apply(this, arguments); // Base widget handling
}
var count = 0;
var onlyDisable = true;
for (var name in arguments[0]) {
if (arguments[0].hasOwnProperty(name)) {
count++;
onlyDisable = onlyDisable && name === 'disabled';
}
}
if (count > 1 || !onlyDisable) {
this._refresh();
}
},
/** Determine if dragging can start.
#memberof Signature
#private
#param {Event} event The triggering mouse event.
#return {boolean} <code>true</code> if allowed, <code>false</code> if not */
_mouseCapture: function(/* event */) {
return !this.options.disabled;
},
/** Start a new line.
#memberof Signature
#private
#param {Event} event The triggering mouse event. */
_mouseStart: function(event) {
this.offset = this.element.offset();
this.offset.left -= document.documentElement.scrollLeft || document.body.scrollLeft;
this.offset.top -= document.documentElement.scrollTop || document.body.scrollTop;
this.lastPoint = [this._round(event.clientX - this.offset.left),
this._round(event.clientY - this.offset.top)];
this.curLine = [this.lastPoint];
this.lines.push(this.curLine);
},
/** Track the mouse.
#memberof Signature
#private
#param {Event} event The triggering mouse event. */
_mouseDrag: function(event) {
var point = [this._round(event.clientX - this.offset.left),
this._round(event.clientY - this.offset.top)];
this.curLine.push(point);
this.ctx.beginPath();
this.ctx.moveTo(this.lastPoint[0], this.lastPoint[1]);
this.ctx.lineTo(point[0], point[1]);
this.ctx.stroke();
this.lastPoint = point;
},
/** End a line.
#memberof Signature
#private
#param {Event} event The triggering mouse event. */
_mouseStop: function(event) {
if (this.curLine.length === 1) {
event.clientY += this.options.thickness;
this._mouseDrag(event);
}
this.lastPoint = null;
this.curLine = null;
this._changed(event);
},
/** Round to two decimal points.
#memberof Signature
#private
#param {number} value The value to round.
#return {number} The rounded value. */
_round: function(value) {
return Math.round(value * 100) / 100;
},
/** Convert the captured lines to JSON text.
#memberof Signature
#return {string} The JSON text version of the lines.
#example var json = $(selector).signature('toJSON') */
toJSON: function() {
return '{"lines":[' + $.map(this.lines, function(line) {
return '[' + $.map(line, function(point) {
return '[' + point + ']';
}) + ']';
}) + ']}';
},
/** Convert the captured lines to SVG text.
#memberof Signature
#return {string} The SVG text version of the lines.
#example var svg = $(selector).signature('toSVG') */
toSVG: function() {
var attrs1 = (this.options.svgStyles ? 'style="fill: ' + this.options.background + ';"' :
'fill="' + this.options.background + '"');
var attrs2 = (this.options.svgStyles ?
'style="fill: none; stroke: ' + this.options.color + '; stroke-width: ' + this.options.thickness + ';"' :
'fill="none" stroke="' + this.options.color + '" stroke-width="' + this.options.thickness + '"');
return '<?xml version="1.0"?>\n<!DOCTYPE svg PUBLIC ' +
'"-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
'<svg xmlns="http://www.w3.org/2000/svg" width="15cm" height="15cm">\n' +
' <g ' + attrs1 + '>\n' +
' <rect x="0" y="0" width="' + this.canvas.width + '" height="' + this.canvas.height + '"/>\n' +
' <g ' + attrs2 + '>\n'+
$.map(this.lines, function(line) {
return ' <polyline points="' +
$.map(line, function(point) { return point + ''; }).join(' ') + '"/>\n';
}).join('') +
' </g>\n </g>\n</svg>\n';
},
/** Convert the captured lines to an image encoded in a <code>data:</code> URL.
#memberof Signature
#param {string} [type='image/png'] The MIME type of the image.
#param {number} [quality=0.92] The image quality, between 0 and 1.
#return {string} The signature as a data: URL image.
#example var data = $(selector).signature('toDataURL', 'image/jpeg') */
toDataURL: function(type, quality) {
return this.canvas.toDataURL(type, quality);
},
/** Draw a signature from its JSON or SVG description or <code>data:</code> URL.
<p>Note that drawing a <code>data:</code> URL does not reconstruct the internal representation!</p>
#memberof Signature
#param {object|string} sig An object with attribute <code>lines</code> being an array of arrays of points
or the text version of the JSON or SVG or a <code>data:</code> URL containing an image.
#example $(selector).signature('draw', sigAsJSON) */
draw: function(sig) {
if (this.options.disabled) {
return;
}
this.clear(true);
if (typeof sig === 'string' && sig.indexOf('data:') === 0) { // Data URL
this._drawDataURL(sig, this.options.scale);
} else if (typeof sig === 'string' && sig.indexOf('<svg') > -1) { // SVG
this._drawSVG(sig, this.options.scale);
} else {
this._drawJSON(sig, this.options.scale);
}
this._changed();
},
/** Draw a signature from its JSON description.
#memberof Signature
#private
#param {object|string} sig An object with attribute <code>lines</code> being an array of arrays of points
or the text version of the JSON.
#param {number} scale A scaling factor. */
_drawJSON: function(sig, scale) {
if (typeof sig === 'string') {
sig = $.parseJSON(sig);
}
this.lines = sig.lines || [];
var ctx = this.ctx;
$.each(this.lines, function() {
ctx.beginPath();
$.each(this, function(i) {
ctx[i === 0 ? 'moveTo' : 'lineTo'](this[0] * scale, this[1] * scale);
});
ctx.stroke();
});
},
/** Draw a signature from its SVG description.
#memberof Signature
#private
#param {string} sig The text version of the SVG.
#param {number} scale A scaling factor. */
_drawSVG: function(sig, scale) {
var lines = this.lines = [];
$(sig).find('polyline').each(function() {
var line = [];
$.each($(this).attr('points').split(' '), function(i, point) {
var xy = point.split(',');
line.push([parseFloat(xy[0]), parseFloat(xy[1])]);
});
lines.push(line);
});
var ctx = this.ctx;
$.each(this.lines, function() {
ctx.beginPath();
$.each(this, function(i) {
ctx[i === 0 ? 'moveTo' : 'lineTo'](this[0] * scale, this[1] * scale);
});
ctx.stroke();
});
},
/** Draw a signature from its <code>data:</code> URL.
<p>Note that this does not reconstruct the internal representation!</p>
#memberof Signature
#private
#param {string} sig The <code>data:</code> URL containing an image.
#param {number} scale A scaling factor. */
_drawDataURL: function(sig, scale) {
var image = new Image();
var context = this.ctx;
image.onload = function() {
context.drawImage(this, 0, 0, image.width * scale, image.height * scale);
};
image.src = sig;
},
/** Determine whether or not any drawing has occurred.
#memberof Signature
#return {boolean} <code>true</code> if not signed, <code>false</code> if signed.
#example if ($(selector).signature('isEmpty')) ... */
isEmpty: function() {
return this.lines.length === 0;
},
/** Remove the signature functionality.
#memberof Signature
#private */
_destroy: function() {
this.element.removeClass(this.widgetFullName || this.widgetBaseClass);
$(this.canvas).remove();
this.canvas = this.ctx = this.lines = null;
this._mouseDestroy();
}
};
if (!$.Widget.prototype._destroy) {
$.extend(signatureOverrides, {
/* Remove the signature functionality. */
destroy: function() {
this._destroy();
$.Widget.prototype.destroy.call(this); // Base widget handling
}
});
}
if ($.Widget.prototype._getCreateOptions === $.noop) {
$.extend(signatureOverrides, {
/* Restore the metadata functionality. */
_getCreateOptions: function() {
return $.metadata && $.metadata.get(this.element[0])[this.widgetName];
}
});
}
$.widget('kbw.signature', $.ui.mouse, signatureOverrides);
// Make some things more accessible
$.kbw.signature.options = $.kbw.signature.prototype.options;
})(jQuery);
</script>
<script>
var sig = $('#sig').signature({syncField: '#signature64', syncFormat: 'PNG'});
$('#clear').click(function(e) {
e.preventDefault();
sig.signature('clear');
$("#signature64").val('');
});
</script>
I use it laravel, hope anyone can help me.
I was also stuck at this, try following:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.3/jquery.ui.touch-punch.min.js" ></script>
When I click on a tooltip the content is shown on the page. The <div> which holds the content of the tooltip is shown in the page. However, when I go back to the previous page the tooltip div is still showing.
I tried adding adding an event listener onHashChange and add element hidden but that didn't work.
Here is the div when going back to a previous page:
I try adding the onHashChange event listener to the hidden() which is in charge of hiding the tooltip by adding a class hidden. Here is the code.
'use strict';
import $ from 'jquery';
import _ from 'underscore';
/**
* Creates a tooltip. The constructor is passed an HTML element that serves as
* the trigger to show or hide the tooltip. The tooltip should have an
* `aria-describedby` attribute, the value of which is the ID of the tooltip
* content to show or hide.
*/
class Tooltip {
/**
* #param {HTMLElement} el - The trigger element for the component.
* #constructor
*/
constructor(el) {
/** #private {HTMLElment} The triggering HTML element. */
this._trigger = el;
/** #private {HTMLElement} The tooltip element. */
this._tooltip = document.getElementById($(el).attr('aria-describedby'));
/** #private {boolean} Whether the tooltip is visible. */
this._active = false;
}
/**
* Sets event listeners, decorates the tooltip element, and appends the
* tooltip to the body to avoid positioning issues.
* #method
* #return {this} Tooltip
*/
init() {
$(this._tooltip).addClass(`${Tooltip.CssClass.TOOLTIP}
${Tooltip.CssClass.HIDDEN}`).attr({
'aria-hidden': true,
'role': 'tooltip'
}).on('click', e => {
// Stop click propagation so clicking on the tip doesn't trigger a
// click on body, which would close the tooltip.
e.stopPropagation();
}).detach().appendTo('body');
$(this._trigger).on('click', e => {
e.preventDefault();
e.stopPropagation();
this.toggle();
});
Tooltip.AllTips.push(this);
return this;
}
/**
* Displays the tooltip. Sets a one-time listener on the body to close the
* tooltip when a click event bubbles up to it.
* #method
* #return {this} Tooltip
*/
show() {
Tooltip.hideAll();
$(this._tooltip).removeClass(Tooltip.CssClass.HIDDEN)
.attr('aria-hidden', false);
$('body').one('click.tooltip', () => {
this.hide();
});
$(window).on('resize.tooltip', _.debounce(() => {
this.reposition();
}, 200));
this.reposition();
this._active = true;
return this;
}
/**
* Hides the tooltip and removes the click event listener on the body.
* #method
* #return {this} Tooltip
*/
hide() {
$(this._tooltip).addClass(Tooltip.CssClass.HIDDEN)
.attr('aria-hidden', true);
$(this._tooltip).onhashchange = function() {
$(this._tooltip).addClass(Tooltip.CssClass.HIDDEN)
.attr('aria-hidden', true);
}
$('body').off('click.tooltip');
this._active = false;
return this;
}
/**
* Hides the tooltip when changing pages.
* #method
* #return {this} Tooltip
*/
/**
* Toggles the state of the tooltip.
* #method
* #return {this} Tooltip
*/
toggle() {
if (this._active) {
this.hide();
} else {
this.show();
}
return this;
}
/**
* Positions the tooltip beneath the triggering element.
* #method
* #return {this} Tooltip
*/
reposition() {
const positioning = {
'left': 'auto',
'position': 'absolute',
'right': 'auto',
'top': 'auto',
'width': ''
};
// TODO(jjandoc): For RTL languages, we should make the default right
// alignment. Right now, the default is left alignment.
// const isRTL = $('html').attr('dir') === 'rtl';
// Reset positioning.
$(this._tooltip).css(positioning);
const triggerOffset = $(this._trigger).offset();
const tooltipWidth = $(this._tooltip).outerWidth();
const viewportWidth = $(window).innerWidth();
const gutter = 15; // Minimum distance from screen edge.
const topPos = triggerOffset.top + $(this._trigger).outerHeight();
let leftPos = 'auto';
let rightPos = 'auto';
// Determine left or right alignment.
// If the tooltip is wider than the screen minus gutters, then position
// the tooltip to extend to the gutters.
if (tooltipWidth >= viewportWidth - (2 * gutter)) {
leftPos = `${gutter}px`;
rightPos = `${gutter}px`;
positioning.width = 'auto';
} else if (triggerOffset.left + tooltipWidth + gutter > viewportWidth) {
// If the tooltip, when left aligned with the trigger, would cause the
// tip to go offscreen (determined by taking the trigger left offset and
// adding the tooltip width and the left gutter) then align the tooltip
// to the right side of the trigger element.
leftPos = 'auto';
rightPos = viewportWidth -
(triggerOffset.left + $(this._trigger).outerWidth()) + 'px';
} else {
// Align the tooltip to the left of the trigger element.
leftPos = `${triggerOffset.left}px`;
rightPos = 'auto';
}
// Set styling positions, reversing left and right if this is an RTL
// language.
positioning.left = leftPos;
positioning.right = rightPos;
positioning.top = topPos;
$(this._tooltip).css(positioning);
return this;
}
}
/**
* Array of all the instantiated tooltips.
* #type {Array<Tooltip>}
*/
Tooltip.AllTips = [];
/**
* Hide all Tooltips.
* #public
*/
Tooltip.hideAll = function() {
_.each(Tooltip.AllTips, tip => {
tip.hide();
});
};
/**
* CSS classes used by this component.
* #enum {string}
*/
Tooltip.CssClass = {
HIDDEN: 'hidden',
TOOLTIP: 'tooltip-bubble',
TRIGGER: 'js-tooltip-trigger'
};
export default Tooltip;
How to remove from DOM when going back one page?
It works great on desktop, iPhone, and Windows phone but it doesn't scratch on the Android phones. I'm not as familiar with Androids but I'm assuming it has something to do with the javascript. How can i fix this?
https://codepen.io/curthusting/pen/fkCzh
HTML 5:
(function() {
var image = { // back and front images
'back': { 'url': 'http://lorempixel.com/600/400/nature/2', 'img': null },
'front': { 'url': 'http://lorempixel.com/600/400/nature/5', 'img': null }
};
var canvas = { 'temp': null, 'draw': null }; // temp and draw canvases
var mouseDown = false;
/**
* Helper function to get the local coords of an event in an element,
* since offsetX/offsetY are apparently not entirely supported, but
* offsetLeft/offsetTop/pageX/pageY are!
*
* #param elem element in question
* #param ev the event
*/
function getLocalCoords(elem, ev) {
var ox = 0,
oy = 0;
var first;
var pageX, pageY;
// Walk back up the tree to calculate the total page offset of the
// currentTarget element. I can't tell you how happy this makes me.
// Really.
while (elem != null) {
ox += elem.offsetLeft;
oy += elem.offsetTop;
elem = elem.offsetParent;
}
if (ev.hasOwnProperty('changedTouches')) {
first = ev.changedTouches[0];
pageX = first.pageX;
pageY = first.pageY;
} else {
pageX = ev.pageX;
pageY = ev.pageY;
}
return { 'x': pageX - ox, 'y': pageY - oy };
}
/**
* Recomposites the canvases onto the screen
*
* Note that my preferred method (putting the background down, then the
* masked foreground) doesn't seem to work in FF with "source-out"
* compositing mode (it just leaves the destination canvas blank.) I
* like this method because mentally it makes sense to have the
* foreground drawn on top of the background.
*
* Instead, to get the same effect, we draw the whole foreground image,
* and then mask the background (with "source-atop", which FF seems
* happy with) and stamp that on top. The final result is the same, but
* it's a little bit weird since we're stamping the background on the
* foreground.
*
* OPTIMIZATION: This naively redraws the entire canvas, which involves
* four full-size image blits. An optimization would be to track the
* dirty rectangle in scratchLine(), and only redraw that portion (i.e.
* in each drawImage() call, pass the dirty rectangle as well--check out
* the drawImage() documentation for details.) This would scale to
* arbitrary-sized images, whereas in its current form, it will dog out
* if the images are large.
*/
function recompositeCanvases() {
var main = document.getElementById('maincanvas');
var tempctx = canvas.temp.getContext('2d');
var mainctx = main.getContext('2d');
// Step 1: clear the temp
canvas.temp.width = canvas.temp.width; // resizing clears
// Step 2: stamp the draw on the temp (source-over)
tempctx.drawImage(canvas.draw, 0, 0);
/* !!!! this way doesn't work on FF:
// Step 3: stamp the foreground on the temp (!! source-out mode !!)
tempctx.globalCompositeOperation = 'source-out';
tempctx.drawImage(image.front.img, 0, 0);
// Step 4: stamp the background on the display canvas (source-over)
//mainctx.drawImage(image.back.img, 0, 0);
// Step 5: stamp the temp on the display canvas (source-over)
mainctx.drawImage(canvas.temp, 0, 0);
*/
// Step 3: stamp the background on the temp (!! source-atop mode !!)
tempctx.globalCompositeOperation = 'source-atop';
tempctx.drawImage(image.back.img, 0, 0);
// Step 4: stamp the foreground on the display canvas (source-over)
mainctx.drawImage(image.front.img, 0, 0);
// Step 5: stamp the temp on the display canvas (source-over)
mainctx.drawImage(canvas.temp, 0, 0);
}
/**
* Draw a scratch line
*
* #param can the canvas
* #param x,y the coordinates
* #param fresh start a new line if true
*/
function scratchLine(can, x, y, fresh) {
var ctx = can.getContext('2d');
ctx.lineWidth = 50;
ctx.lineCap = ctx.lineJoin = 'round';
ctx.strokeStyle = '#f00'; // can be any opaque color
if (fresh) {
ctx.beginPath();
// this +0.01 hackishly causes Linux Chrome to draw a
// "zero"-length line (a single point), otherwise it doesn't
// draw when the mouse is clicked but not moved:
ctx.moveTo(x + 0.01, y);
}
ctx.lineTo(x, y);
ctx.stroke();
}
/**
* Set up the main canvas and listeners
*/
function setupCanvases() {
var c = document.getElementById('maincanvas');
// set the width and height of the main canvas from the first image
// (assuming both images are the same dimensions)
c.width = image.back.img.width;
c.height = image.back.img.height;
// create the temp and draw canvases, and set their dimensions
// to the same as the main canvas:
canvas.temp = document.createElement('canvas');
canvas.draw = document.createElement('canvas');
canvas.temp.width = canvas.draw.width = c.width;
canvas.temp.height = canvas.draw.height = c.height;
// draw the stuff to start
recompositeCanvases();
/**
* On mouse down, draw a line starting fresh
*/
function mousedown_handler(e) {
var local = getLocalCoords(c, e);
mouseDown = true;
scratchLine(canvas.draw, local.x, local.y, true);
recompositeCanvases();
if (e.cancelable) { e.preventDefault(); }
return false;
};
/**
* On mouse move, if mouse down, draw a line
*
* We do this on the window to smoothly handle mousing outside
* the canvas
*/
function mousemove_handler(e) {
if (!mouseDown) { return true; }
var local = getLocalCoords(c, e);
scratchLine(canvas.draw, local.x, local.y, false);
recompositeCanvases();
if (e.cancelable) { e.preventDefault(); }
return false;
};
/**
* On mouseup. (Listens on window to catch out-of-canvas events.)
*/
function mouseup_handler(e) {
if (mouseDown) {
mouseDown = false;
if (e.cancelable) { e.preventDefault(); }
return false;
}
return true;
};
c.addEventListener('mousedown', mousedown_handler, false);
c.addEventListener('touchstart', mousedown_handler, false);
window.addEventListener('mousemove', mousemove_handler, false);
window.addEventListener('touchmove', mousemove_handler, false);
window.addEventListener('mouseup', mouseup_handler, false);
window.addEventListener('touchend', mouseup_handler, false);
}
/**
* Set up the DOM when loading is complete
*/
function loadingComplete() {
var loading = document.getElementById('loading');
var main = document.getElementById('main');
loading.className = 'hidden';
main.className = '';
}
/**
* Handle loading of needed image resources
*/
function loadImages() {
var loadCount = 0;
var loadTotal = 0;
var loadingIndicator;
function imageLoaded(e) {
loadCount++;
if (loadCount >= loadTotal) {
setupCanvases();
loadingComplete();
}
}
for (k in image)
if (image.hasOwnProperty(k))
loadTotal++;
for (k in image)
if (image.hasOwnProperty(k)) {
image[k].img = document.createElement('img'); // image is global
image[k].img.addEventListener('load', imageLoaded, false);
image[k].img.src = image[k].url;
}
}
/**
* Handle page load
*/
window.addEventListener('load', function() {
var resetButton = document.getElementById('resetbutton');
loadImages();
resetButton.addEventListener('click', function() {
// clear the draw canvas
canvas.draw.width = canvas.draw.width;
recompositeCanvases()
return false;
}, false);
}, false);
})();
* {
margin: 0;
padding: 0;
}
h1 {
width: 100%;
text-align: center;
}
.cell {
display: table-cell;
vertical-align: middle;
text-align: center;
}
.cell canvas {
vertical-align: middle;
}
.canthumb {
border: 0px solid #222;
}
#main {
margin: 0 auto;
width: 600px;
}
#maincanvas {
border: 0px solid #222;
cursor: pointer;
margin: 0 auto;
}
<div id="main">
<h1>"Scratch off" image to reveal a different one</h1>
<div><canvas id="maincanvas"></canvas></div>
<div>
<input id="resetbutton" type="button" value="Reset"></input>
</div>
</div>
What I have :
As per Google suggestion we have used MVC object and the events DistanceWidget & RadiusWidget to display the radius while re-sizing the circle which is working fine for existing circle (circle drawn by DistanceWidget).
Fiddle Demo
What I need :
I need to show the radius of the circle while drawing a new circle. The event DistanceWidget & RadiusWidget are used only for existing circle (circle drawn by DistanceWidget) not for new circle (user drawn circle by using DrawingManager tool).
Is it possible to show DistanceWidget for creating new circle?
Fiddle
a nice challenge indeed. As #DaveAlperovich has commented, you can't use the DrawingManager to retrieve this piece of information; While drawing, you don't have any access to the circle; You have to wait for the DrawingManager to trigger the circlecomplete event to get a reference to this circle.
Nevertheless, if you can't have a real manager, just fake it.
See the snippet and the description right below.
var FakeDrawer = function(controlDiv, map) {
var self = this;
/* Initialization, some styling ... */
self._map = map;
self.initControls(controlDiv);
/* Setup the click event listener: drawingmode for the circle control */
google.maps.event.addDomListener(self._controls.circle, 'click', function() {
/* Ensure consistency */
self.reset();
/* Bind the drawing mode */
self._map.setOptions({
draggableCursor: "crosshair"
});
self._drawListener = self._map.addListener('mousedown', self.drawingMode(self));
});
/* Just reset things for the stop controls */
google.maps.event.addDomListener(self._controls.stop, 'click', function() {
self.reset();
});
};
FakeDrawer.prototype.drawingMode = function(self) {
return function(center) {
/* Let's freeze the map during drawing */
self._map.setOptions({
draggable: false
});
/* Create a new circle which will be manually scaled */
var circle = new google.maps.Circle({
fillColor: '#000',
fillOpacity: 0.3,
strokeWeight: 2,
clickable: false,
editable: false,
map: self._map,
radius: 1,
center: center.latLng,
zIndex: 1
});
/* Update the radius on each mouse move */
var onMouseMove = self._map.addListener('mousemove', function(border) {
var radius = 1000 * self.distanceBetweenPoints(center.latLng, border.latLng);
circle.setRadius(radius);
/* Here is the feature, know the radius while drawing */
google.maps.event.trigger(self, 'drawing_radius_changed', circle);
});
/* The user has finished its drawing */
google.maps.event.addListenerOnce(self._map, 'mouseup', function() {
/* Remove all listeners as they are no more required */
google.maps.event.removeListener(onMouseMove);
circle.setEditable(true);
/* Restore some options to keep a consistent behavior */
self.reset();
/* Notify listener with the final circle */
google.maps.event.trigger(self, 'circlecomplete', circle);
});
};
};
FakeDrawer.prototype.reset = function() {
var self = this;
self._map.setOptions({
draggableCursor: "",
draggable: "true"
});
/* Remove any applied listener */
if (self._drawListener) {
google.maps.event.removeListener(self._drawListener);
}
};
/* Create views and associated css */
FakeDrawer.prototype.initControls = function(controlDiv) {
var self = this;
function createControlUI(title, image) {
var controlUI = document.createElement('div');
controlUI.style.backgroundColor = '#fff';
controlUI.style.border = '1px solid rgba(0, 0, 0, .15)';
controlUI.style.boxShadow = '1 4px -1px rgba(0, 0, 0, .3)';
controlUI.style.marginTop = '10px';
controlUI.style.textAlign = 'center';
controlUI.style.width = '25px';
controlUI.style.height = '25px';
controlUI.style.display = 'inline-block';
controlUI.title = title;
if (image == "circle") {
controlUI.style.borderLeft = "none";
}
var controlImgWrapper = document.createElement('div');
controlImgWrapper.style.width = '16px';
controlImgWrapper.style.height = '16px';
controlImgWrapper.style.overflow = 'hidden';
controlImgWrapper.style.display = 'inline-block';
controlImgWrapper.style.marginTop = '4px';
controlUI.appendChild(controlImgWrapper);
var imageOffset = {
"circle": 0,
"openhand": -9 * 16
}[image];
var controlImg = document.createElement('img');
controlImg.src = 'https://maps.gstatic.com/mapfiles/drawing.png';
controlImg.style.marginTop = imageOffset + "px";
controlImgWrapper.appendChild(controlImg);
var focusBackground = function() {
controlUI.style.backgroundColor = '#eee';
};
var unfocusBackground = function() {
controlUI.style.backgroundColor = "#fff";
};
controlImg.addEventListener('mouseenter', focusBackground);
controlImg.addEventListener('mouseout', unfocusBackground);
controlUI.addEventListener('mouseenter', focusBackground);
controlUI.addEventListener('mouseout', unfocusBackground);
return controlUI;
}
self._controls = {
circle: createControlUI("Draw a circle", "circle"),
stop: createControlUI("Stop drawing", "openhand"),
};
controlDiv.appendChild(self._controls.stop);
controlDiv.appendChild(self._controls.circle);
};
FakeDrawer.prototype.distanceBetweenPoints = function(p1, p2) {
if (!p1 || !p2) {
return 0;
}
var R = 6371;
var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c;
return d;
};
function InitializeMap() {
var latlng = new google.maps.LatLng(29.760193, -95.36939);
var myOptions = {
zoom: 12,
center: latlng,
zoomControl: true,
mapTypeId: google.maps.MapTypeId.ROADMAP,
disableDefaultUI: true
};
var map = new google.maps.Map(document.getElementById("map"), myOptions);
/* Add a custom control */
var fakeDrawerDiv = document.createElement('div');
var fakeDrawer = new FakeDrawer(fakeDrawerDiv, map);
fakeDrawerDiv.index = 1;
map.controls[google.maps.ControlPosition.TOP_CENTER].push(fakeDrawerDiv);
var updateInfo = function(circle) {
document.getElementById("info").innerHTML = "Radius: " + circle.getRadius();
};
google.maps.event.addListener(fakeDrawer, 'drawing_radius_changed', updateInfo);
google.maps.event.addListener(fakeDrawer, 'circlecomplete', function(circle) {
google.maps.event.addListener(circle, 'radius_changed', function() {
updateInfo(circle);
});
});
}
google.maps.event.addDomListener(window, 'load', InitializeMap);
html,
body {
height: 100%;
margin: 0px;
padding: 0px
}
#map {
height: 80%;
width: 100%;
}
<script src="https://maps.googleapis.com/maps/api/js?v=3&libraries=drawing&ext=.js"></script>
<div id="map"></div>
<div id="info"></div>
Step 1: Create a custom control
Somewhere in the file or as an external library:
var FakeDrawer = function (controlDiv, map) {
var self = this;
/* Initialization, some styling ... */
self._map = map;
self.initControls(controlDiv);
};
FakeDrawer.prototype.initControls(controlDiv) {
var self = this;
function createControlUI (title, image) {
var controlUI = document.createElement('div');
/* ... See the snippet for details .. just some styling */
return controlUI;
}
self._controls = {
circle: createControlUI("Draw a circle", "circle"),
stop: createControlUI("Stop drawing", "openhand"),
};
controlDiv.appendChild(self._controls.stop);
controlDiv.appendChild(self._controls.circle);
};
Step 2: Add some sugars
This are functions that we may use; Highly inspired from your JsFiddle :)
A reset method to recover a consistent state when needed:
FakeDrawer.prototype.reset = function () {
var self = this;
self._map.setOptions({
draggableCursor: "",
draggable: "true"
});
/* Remove any applied listener */
if (self._drawListener) { google.maps.event.removeListener(self._drawListener) ; }
};
And, a distance computer:
FakeDrawer.prototype.distanceBetweenPoints = function (p1, p2) {
if (!p1 || !p2) {
return 0;
}
var R = 6371;
var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c;
return d;
};
Step 3: Create your own drawing mode
Now that we have some controls, we have to define their behavior. The stop control is straightforward; Let's have a look to the circle control.
FakeDrawer.prototype.drawingMode = function (self) {
return function (center) {
/* Let's freeze the map during drawing */
self._map.setOptions({draggable: false});
/* Create a new circle which will be manually scaled */
var circle = new google.maps.Circle({
fillColor: '#000',
fillOpacity: 0.3,
strokeWeight: 2,
clickable: false,
editable: false,
map: self._map,
radius: 1,
center: center.latLng,
zIndex: 1
});
/* Update the radius on each mouse move */
var onMouseMove = self._map.addListener('mousemove', function (border) {
var radius = 1000 * self.distanceBetweenPoints(center.latLng, border.latLng);
circle.setRadius(radius);
/* Here is the feature, know the radius while drawing */
google.maps.event.trigger(self, 'drawing_radius_changed', circle);
});
/* The user has finished its drawing */
google.maps.event.addListenerOnce(self._map, 'mouseup', function () {
/* Remove all listeners as they are no more required */
google.maps.event.removeListener(onMouseMove);
circle.setEditable(true);
/* Restore some options to keep a consistent behavior */
self.reset();
/* Notify listener with the final circle */
google.maps.event.trigger(self, 'circlecomplete', circle);
});
};
};
Step 4: Bind controls
Now that everything is okay, let's add some listeners to the initial version of the constructor so that each control has a corresponding action when clicked.
var FakeDrawer = function (controlDiv, map) {
var self = this;
/* Initialization, some styling ... */
self._map = map;
self.initControls(controlDiv);
/* Setup the click event listeners: drawingmode */
google.maps.event.addDomListener(self._controls.circle, 'click', function() {
/* Ensure consistency */
self.reset();
/* Only drawingmode */
self._map.setOptions({draggableCursor: "crosshair"});
self._drawListener = self._map.addListener('mousedown', self.drawingMode(self));
});
google.maps.event.addDomListener(self._controls.stop, 'click', function () {
self.reset();
});
};
Step 5: Use it!
Assuming that your map has been initialized correctly.
Inside your map init function:
var fakeDrawerDiv = document.createElement('div');
var fakeDrawer = new FakeDrawer(fakeDrawerDiv, map);
fakeDrawerDiv.index = 1;
map.controls[google.maps.ControlPosition.TOP_CENTER].push(fakeDrawerDiv);
var updateInfo = function (circle) {
document.getElementById("info").innerHTML = "Radius: " + circle.getRadius();
};
google.maps.event.addListener(fakeDrawer, 'drawing_radius_changed', updateInfo);
google.maps.event.addListener(fakeDrawer, 'circlecomplete', function (circle) {
google.maps.event.addListener(circle, 'radius_changed', function () {
updateInfo(circle);
});
});
Enjoy, hope it will help.
Is it possible to create two layers (with one being translucent) in OpenLayers and move them independently? If so, how?
I want to let the user choose which layer to move or if that's not possible, move one layer via my own JavaScript code while the other is controlled by the user.
Both will be prerendered pixmap layers, if that is important.
This is the solution I came up with. It isn't pretty but it works for my purposes.
Better alternatives are very welcome ...
/**
* #requires OpenLayers/Layer/TMS.js
*/
MyLayer = OpenLayers.Class(OpenLayers.Layer.TMS, {
latShift: 0.0,
latShiftPx: 0,
setMap: function(map) {
OpenLayers.Layer.TMS.prototype.setMap.apply(this, arguments);
map.events.register("moveend", this, this.mapMoveEvent)
},
// This is the function you will want to modify for your needs
mapMoveEvent: function(event) {
var resolution = this.map.getResolution();
var center = this.map.getCenter();
// This is some calculation I use, replace it whatever you like:
var h = center.clone().transform(projmerc, proj4326);
var elliptical = EllipticalMercator.fromLonLat(h.lon, h.lat);
var myCenter = new OpenLayers.LonLat(elliptical.x, elliptical.y);
this.latShift = myCenter.lat - center.lat;
this.latShiftPx = Math.round(this.latShift/resolution);
this.div.style.top = this.latShiftPx + "px";
},
moveTo: function(bounds, zoomChanged, dragging) {
bounds = bounds.add(0, this.latShift);
OpenLayers.Layer.TMS.prototype.moveTo.apply(this, [bounds, zoomChanged, dragging]);
},
// mostly copied and pasted from Grid.js ...
moveGriddedTiles: function() {
var buffer = this.buffer + 1;
while(true) {
var tlTile = this.grid[0][0];
var tlViewPort = {
x: tlTile.position.x +
this.map.layerContainerOriginPx.x,
y: tlTile.position.y +
this.map.layerContainerOriginPx.y + this.latShiftPx // ... except this line
};
var ratio = this.getServerResolution() / this.map.getResolution();
var tileSize = {
w: Math.round(this.tileSize.w * ratio),
h: Math.round(this.tileSize.h * ratio)
};
if (tlViewPort.x > -tileSize.w * (buffer - 1)) {
this.shiftColumn(true, tileSize);
} else if (tlViewPort.x < -tileSize.w * buffer) {
this.shiftColumn(false, tileSize);
} else if (tlViewPort.y > -tileSize.h * (buffer - 1)) {
this.shiftRow(true, tileSize);
} else if (tlViewPort.y < -tileSize.h * buffer) {
this.shiftRow(false, tileSize);
} else {
break;
}
}
},
CLASS_NAME: "MyLayer"
});
Note that this only works with OpenLayers 2.13 or newer