I have a div with absolute positioning set to allow vertical scrolling. My app includes drag & drop facilities that rely on me determining the coordinates of elements when events are fired.
The offsets I use to calculate elements positions (i.e. element.offsetLeft & element.offsetTop) only relate to original position of the element and do not account for changes in position that result from the user having scrolled. I figured I could add in a correction if I could calculate the distance scrolled but I can't see any way to do that (unlike with window scrolling).
Would really appreciate any suggestions.
Take a look at the scrollTop and scrollLeft properties of the div container.
Here's a cross-browser solution that finds an element's position taking into account scrolling div/s and window scroll:
var isIE = navigator.appName.indexOf('Microsoft Internet Explorer') != -1;
function findElementPosition(_el){
var curleft = 0;
var curtop = 0;
var curtopscroll = 0;
var curleftscroll = 0;
if (_el.offsetParent){
curleft = _el.offsetLeft;
curtop = _el.offsetTop;
/* get element scroll position */
var elScroll = _el;
while (elScroll = elScroll.parentNode) {
curtopscroll = elScroll.scrollTop ? elScroll.scrollTop : 0;
curleftscroll = elScroll.scrollLeft ? elScroll.scrollLeft : 0;
curleft -= curleftscroll;
curtop -= curtopscroll;
}
/* get element offset postion */
while (_el = _el.offsetParent) {
curleft += _el.offsetLeft;
curtop += _el.offsetTop;
}
}
/* get window scroll position */
var offsetX = isIE ? document.body.scrollLeft : window.pageXOffset;
var offsetY = isIE ? document.body.scrollTop : window.pageYOffset;
return [curtop + offsetY,curleft + offsetX];
}
This is what I'm implementing as a correction in case anyone's interested.
Thanks guys.
/*
Find a html element's position.
Adapted from Peter-Paul Koch of QuirksMode at http://www.quirksmode.org/js/findpos.html
*/
function findPos(obj)
{
var curleft = 0;
var curtop = 0;
var curxscroll = 0;
var curyscroll =0;
while(obj && obj.offsetParent)
{
curyscroll = obj.offsetParent.scrollTop || 0;
curxscroll = obj.offsetParent.scrollLeft || 0;
curleft += obj.offsetLeft - curxscroll;
curtop += obj.offsetTop - curyscroll;
obj = obj.offsetParent;
}
return [curleft,curtop];
}
Related
is there a way of paning the bubble in the current view when there are regions which are outside the mapview?
E.g. https://dev2.gruppenunterkuenfte.de/nordrhein-westfalen__r187.html?vs=1
You can click on a bubble at the edge and you see them outside.
Using google: https://www.gruppenunterkuenfte.de/nordrhein-westfalen__r187.html?vs=1
will pan automatically in the full view ...
Regards
Chris
Might not be available out of the box, but something as follows could be done to check when opening the bubble and move the map center.
var checkBubble = function(evt) {
setTimeout(function() {
if(infoBubble && infoBubble.getState() == "open"){
var border = 50;
var objRect = infoBubble.getContentElement().parentElement.getBoundingClientRect();
var objStyleRight = Math.abs(parseInt(infoBubble.getContentElement().parentElement.style.right));
objStyleRight = objStyleRight ? objStyleRight : 0;
var mapRect = map.getElement().getBoundingClientRect();
var shiftX = 0;
var shiftY = 0;
// check, if infobubble isn't too far to up
if ((objRect.top-border) < mapRect.top) {
shiftY = (mapRect.top - (objRect.top-border));
}
// check, if infobubble isn't too far to the left
var objLeft = (objRect.left - objStyleRight);
if ((objLeft-border) < mapRect.left) {
shiftX = (mapRect.left - (objLeft-border));
} // check, if infobubble isn't too far to the right
else if ((objRect.right+border) > mapRect.right) {
shiftX = -(objRect.right - (mapRect.right-border));
}
if ((shiftX == 0) && (shiftY == 0)) {
return;
}
var currScreenCenter = map.geoToScreen(map.getCenter());
var newY = (currScreenCenter.y - shiftY);
var newX = (currScreenCenter.x - shiftX);
var newGeoCenter = map.screenToGeo(newX, newY);
map.setCenter(newGeoCenter, true);
}
}, 20);
}
map.addEventListener("mapviewchange",checkBubble);
Thanks a lot works great! I extend to the case that the bubble is outside at the bottom:
...
// check, if infobubble isn't too far to up
if ((objRect.top-border) < mapRect.top) {
shiftY = (mapRect.top - (objRect.top-border));
} else {
if ((objRect.bottom+border) > mapRect.bottom) {
shiftY = -(objRect.bottom - (mapRect.bottom-border));
}
}
...
Regards
Chris
I need to view the segments and handles of the path that defines a SymbolItem. It is a related issue to this one but in reverse (I want the behavior displayed on that jsfiddle).
As per the following example, I can view the bounding box of the SymbolItem, but I cannot select the path itself in order to view its segments/handles. What am I missing?
function onMouseDown(event) {
project.activeLayer.selected = false;
// Check whether there is something on that position already. If there isn't:
// Add a circle centered around the position of the mouse:
if (event.item === null) {
var circle = new Path.Circle(new Point(0, 0), 10);
circle.fillColor = '#' + Math.floor(Math.random() * 16777215).toString(16);
var circleSymbol = new SymbolDefinition(circle);
multiply(circleSymbol, event.point);
}
// If there is an item at that position, select the item.
else {
event.item.selected = true;
}
}
function multiply(item, location) {
for (var i = 0; i < 10; i++) {
var next = item.place(location);
next.position.x = next.position.x + 20 * i;
}
}
Using SymbolDefinition/SymbolItem prevent you from changing properties of each symbol items.
The only thing you can do in this case is select all symbols which share a common definition.
To achieve what you want, you have to use Path directly.
Here is a sketch showing the solution.
function onMouseDown(event) {
project.activeLayer.selected = false;
if (event.item === null) {
var circle = new Path.Circle(new Point(0, 0), 10);
circle.fillColor = Color.random();
// just pass the circle instead of making a symbol definition
multiply(circle, event.point);
}
else {
event.item.selected = true;
}
}
function multiply(item, location) {
for (var i = 0; i < 10; i++) {
// use passed item for first iteration, then use a clone
var next = i === 0 ? item : item.clone();
next.position = location + [20 * i, 0];
}
}
In my demo. I'm able to create a few ellipses that overlay each other. Each ellipse is slightly rotated and when click should stretch outwards.
However, right now I'm using .scale(x,y) and the ellipses's height increases vertically.
I'm not sure how I would accomplish this type of effect using paper.js
DEMO
Code Pen Demo
paper.install(window);
var canvas = document.getElementById("myCanvas");
window.onload = function() {
paper.setup('myCanvas');
var numberOfRings = 6,
rings = [],
size = [225,400],
colors = ['black','green','orange','blue','yellow','grey'],
max_frame = 50,
negative_scale = 0.99,
positive_scale = 1.01;
for(var i = 0; i < numberOfRings; i++)
{
var path = new Path.Ellipse({
center:view.center,
size: size,
strokeColor: colors[i],
strokeWidth :10
});
var rotate = 30*i +30;
path.rotate(rotate);
path.animation = false;
path.rotateValue = rotate;
path.animationStartFrame = 0;
path.animationScale = positive_scale;
path.smooth();
path.animationIndex = i;
path.onClick = function(event) {
rings[this.animationIndex].animation = true;
}
rings.push(path);
}
view.onFrame = function(event) {
for(var i = 0; i < numberOfRings; i++)
{
if (rings[i].animation == true){
if (rings[i].animationStartFrame == 0)
{
rings[i].animationStartFrame = event.count;
}
if (rings[i].animationStartFrame > 0 && event.count < (rings[i].animationStartFrame + max_frame)){
// TODO
rings[i].scale(1,rings[i].animationScale);
} else if ( event.count > (rings[i].animationStartFrame + max_frame)){
rings[i].animation = false;
rings[i].animationStartFrame = 0;
if (rings[i].animationScale == negative_scale)
rings[i].animationScale = positive_scale;
else
rings[i].animationScale = negative_scale;
}
}
}
}
}
canvas{
width: 100%;
height: 100%;
}
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.11.5/paper-full.min.js"></script>
</head>
<body>
<canvas id="myCanvas" resize></canvas>
</body>
</html>
first for this kind of things i think its easier to use paper.js / items with the applyMatrix option set to false - this way transformations are not applied / baked into the item/children/paths but stay separate in the transformation and so can also get manipulated later in an absolute fashion..
additionally to get to your desired effect i used a trick: Group
i encapsulate the ellipse path in an group. so i can rotate the group only.
the coordinate-system of the child is not modified - and i can manipulate the ellipses scaling as in its original coordinate-system / as it was not rotated..
i have made some other modifications based on your example - mainly so its easier to test in different canvas-sizes and with different numbers of ellipses.
i first tested / developed it at sketch.paperjs.org (i find it easier to test/debug it there)
and then converted it to fit the the plain js version here.
if you want to do more complex animations please checkout the great library animatePaper.js - i used it heavily and loved to work with it :-)
it supports simple and more complex animations of attributes of paper-objects.
paper.install(window);
var canvas = document.getElementById('myCanvas');
window.onload = function() {
paper.setup('myCanvas');
var numberOfRings = 5;
var rings = [];
let view_max_length = Math.min(view.size.width, view.size.height);
var ringStartSize = new Size(view_max_length * 0.4, view_max_length * 0.45);
var ringTargetLength = view_max_length * 0.9;
var scaleStepSize = 0.01;
function createRing(index) {
let path = new Path.Ellipse({
center: view.center,
size: ringStartSize,
strokeColor: {
hue: (360 / numberOfRings) * index,
saturation: 1,
brightness: 1
},
strokeWidth: 10,
applyMatrix: false,
strokeScaling: false,
});
// add custom properties
path.animate = false;
path.animationDirectionOut = true;
path.animationIndex = index;
path.onClick = function(event) {
//console.log(this);
this.animate = true;
};
let rotationgroup = new Group(path);
rotationgroup.applyMatrix = false;
let offsetAngle = (360 / 2) / numberOfRings;
let rotate = offsetAngle * index;
rotationgroup.pivot = path.bounds.center;
rotationgroup.rotation = rotate;
return path;
}
function init() {
for (let i = 0; i < numberOfRings; i++) {
rings.push(createRing(i));
}
}
function animateRing(event, ring) {
if (ring.animate) {
let tempScaleStep = scaleStepSize;
if (!ring.animationDirectionOut) {
tempScaleStep = tempScaleStep * -1;
}
ring.scaling.y += tempScaleStep;
// test if we have reached destination size.
if (
(ring.bounds.height >= ringTargetLength) ||
(ring.bounds.height <= ringStartSize.height)
) {
ring.animate = false;
// change direction
ring.animationDirectionOut = !ring.animationDirectionOut;
}
}
}
view.onFrame = function(event) {
if (rings && (rings.length > 0)) {
for (var i = 0; i < numberOfRings; i++) {
animateRing(event, rings[i]);
}
}
};
init();
}
canvas {
widht: 100%;
height: 100%;
background-color: rgb(0,0,50);
}
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.11.5/paper-full.min.js"></script>
</head>
<body>
<canvas id="myCanvas" resize></canvas>
</body>
</html>
I have the following fiddle which distills an issue I am having with a larger project
http://jsfiddle.net/zhaocnus/6N3v8/
in Firefox and Safari, this animation will start having a jittering effect left and right on odd zoom levels (zoom in/out using Ctrl+/- or Cmd+/- on Mac). I believe this is do to sub-pixel rendering issues and the differences between the various browsers round up or down pixels during the zoom calculations, but I have no idea how to fix it and am looking for any suggestions.
I can't use more modern CSS3 animation features as I need to support legacy browsers like IE7.
(code from fiddle below, can't seem to post without it, although not sure it makes sense without CSS and HTML)
// js spritemap animation
// constants
var COUNTER_MAX = 9,
OFFSET = -50,
FRAMERATE = 100;
// variables
var _counter = 0,
_animElm = document.getElementById('animation'),
_supportBgPosX = false;
// functions
function play() {
// update counter
_counter++;
if (_counter > COUNTER_MAX) {
_counter = 0;
}
// show current frame
if (_supportBgPosX) {
_animElm.style.backgroundPositionX = (_counter * OFFSET) + 'px';
} else {
_animElm.style.backgroundPosition = (_counter * OFFSET) + 'px 0';
}
// next frame
setTimeout(play, FRAMERATE);
}
// check if browser support backgroundPositionX
if (_animElm.style.backgroundPositionX != undefined) {
_supportBgPosX = true;
}
// start animation
play();
Instead of moving the background to the new frame, have a canvas tag re-draw the frame. The canvas tag handles sub-pixel interpretation independent of the browser and because of this you not only control the render (browser agnostic) but also solve the jitter issue as it's being re-drawn frame-by-frame into the canvas' dimensions in realtime.
Zooming is specifically tough because there's no reliable way to detect the zoom level from the browser using jQuery or plain-ole javascript.
Check out the demo here: http://jsfiddle.net/zhaocnus/Gr9TF/
*Credit goes to my coworker zhaocnus for the solution. I'm simply answering this question on his behalf.
// js spritemap animation
// constants
var COUNTER_MAX = 14,
OFFSET = -200,
FRAMERATE = 100;
// variables
var _counter = 0,
_sprite = document.getElementById("sprite"),
_canvas = document.getElementById("anim-canvas"),
_ctx = _canvas.getContext("2d"),
_img = null;
// functions
function play() {
// update counter
_counter++;
if (_counter > COUNTER_MAX) {
_counter = 0;
}
// show current frame
_ctx.clearRect(0, 0, _canvas.width, _canvas.height);
_ctx.drawImage(_img, _counter * OFFSET, 0);
// next frame
setTimeout(play, FRAMERATE);
}
function getStyle(oElm, strCssRule) {
var strValue = '';
if (document.defaultView && document.defaultView.getComputedStyle) {
strValue = document.defaultView.getComputedStyle(oElm, null).getPropertyValue(strCssRule);
} else if (oElm.currentStyle) {
var strCssRule = strCssRule.replace(_styleRegExp, function (strMatch, p1) {
return p1.toUpperCase();
});
strValue = oElm.currentStyle[strCssRule];
}
return String(strValue);
}
function initCanvas(callback) {
var url = getStyle(_sprite, 'background-image');
url = url.replace(/^url\(["']?/, '').replace(/["']?\)$/, '');
_img = new Image();
_img.onload = function(){
_ctx.drawImage(_img, 0, 0);
callback();
};
_img.src = url;
}
// start animation
initCanvas(play);
I've got a whole bunch of rects on my canvas.
I'd like to change the stroke on whatever rect the user clicks, as well as running some other javascript. My simplified code is below.
var canvas = Raphael("test");
var st = canvas.set();
for (var i = 0; i < 2; i++) {
var act = canvas.rect(///edited for brevity////).attr({"stroke":"none"});
st.push(act)
act.node.onclick = function() {
st.attr({stroke: "none"});
act.attr({stroke: "yellow"});
}
}
Right now, no matter what rect I click on, it's only changing the stroke on the last rect drawn.
Any ideas?
Not a Raphaƫl problem but rather lack of closure understanding. Easily could be fixed by self invoking function:
for (var i = 0; i < 2; i++) {
var act = canvas.rect(///edited for brevity////).attr({"stroke":"none"});
st.push(act)
(function (act) {
act.node.onclick = function() {
st.attr({stroke: "none"});
act.attr({stroke: "yellow"});
}
})(act);
}
//Try and then embellish
st[i].click(function (e)
{
this.attr({stroke: "yellow"});
}