In a UIWebView I need to access properties of the DOM element (from a SVG graph) when I longTap on it. To do so, I've added a UILongPressGestureRecognizer as following :
UILongPressGestureRecognizer* longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action: #selector(longPress:)];
[self.webView addGestureRecognizer: longPress];
When I longPress on the view, the handler get called from which I call a JS function :
- (void) longPress: (UIGestureRecognizer *) gesture {
CGPoint curCoords = [gesture locationInView:self.webView];
if (!CGPointEqualToPoint(curCoords, self.lastLongPress)) {
self.lastLongPress = curCoords;
[self.webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:#"longPress(%f, %f)", curCoords.x, curCoords.y]];
}
}
This is my JS handler :
function longPress(x, y) {
x = x + window.pageXOffset;
y = y + window.pageYOffset;
var element = svgDocument.elementFromPoint(x, y);
alert(element.localName + ' ' + x + ' ' + y + ' ' + window.innerWidth + ' ' + window.innerHeight);
}
However it appears that UIWebView coordinates are != from the DOM coordinates (where I click does not correspond to the localName shown in the alert). I've managed to figure out that there is +/- a 1.4 factor between UIWebView coordinates & JS ones (by clicking in the lower-right hand side of the screen and comparing these values with window.innder{Width,Height}.
My guess was that UIWebView might apply a default Zoom ratio initially, but I can't find what this values corresponds to.
Moreover, I'll also need a way to make this work when the user actually zooms/moves the page.
Does anybody knows what I'm doing wrong ?
Thanks,
Okay, I finally found what's the problem.
It was coming from the zoom ratio, and here is how I managed to fix it :
- (void) longPress: (UIGestureRecognizer *) gesture {
int displayWidth = [[self.webView stringByEvaluatingJavaScriptFromString:#"window.innerWidth"] intValue];
CGFloat scale = self.webView.frame.size.width / displayWidth;
CGPoint curCoords = [gesture locationInView:self.webView];
curCoords.x /= scale;
curCoords.y /= scale;
if (!CGPointEqualToPoint(curCoords, self.lastLongPress)) {
self.lastLongPress = curCoords;
[self.webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:#"longPress(%f, %f)", curCoords.x, curCoords.y]];
}
}
And the JS Handler :
function longPress(x, y) {
var e = svgDocument.elementFromPoint(x, y);
alert('Youhouu ' + e.localName);
}
It appears that it is not needed to add the pageOffset as now UIWebView automatically adds it (from iOS 5 I believe).
Cheers,
Related
I'm using ABC Dinamo's resource on variable fonts but am getting a bit stuck. I'm trying to control font weight in relation to the user's mouse movements, for which they provide this script:
function updateText(e) {
multiplierWidth = e.offsetX / window.innerWidth;
multiplierHeight = e.offsetY / window.innerHeight;
randomWeight = multiplierWidth * (200 - 35) + 35;
randomWidth = multiplierHeight * (200 - 100) + 100;
myText.style.fontVariationSettings = "\"wght\" " + randomWeight + ", \"wdth\" " + randomWidth;
}
window.addEventListener("mousemove", updateText)
However, the font I'm using only has one variable axis, weight, from 200 to 800. The code they provide is for a font that has two, weight and width.
I've tried to use the code as is (but with 'myText.style' replaced with the h7 tag I need to use), but that doesn't work. I've tried using this edited version, but it doesn't work either:
function updateText(e) {
multiplierWidth = e.offsetX / window.innerWidth;
multiplierHeight = e.offsetY / window.innerHeight;
randomWeight = multiplierWidth * (200 - 35) + 35;
h7.fontVariationSettings = "\"wght\" " + randomWeight;
}
window.addEventListener("mousemove", updateText)
Can anyone tell me where I'm going wrong please? Am I missing parts of the script? The guide is useful to a point but I think it's not quite clear enough for script novices like me!
I am using JavaFX WebView included in jdk-8u45 to open a web page which shows a map using OpenLayers 2.13.1. I'm trying to zoom in on the map using a ZoomBox with a BoxHandler. The zooming works like it should, but the problem is how the the rectangle is drawn.
The wanted result is that once I click on the map and start dragging, the rectangle should start drawing as I move the mouse. This works fine in all browsers, except inside my WebView. What happens is that only after I have moved my mouse a few cm in both x- and y-direction (e.g. diagonally), the rectangle starts drawing from this position (not the one where I started dragging). I have looked at the coordinates from the different mouse events, and they all seem to be correct, which is confirmed by the fact that it zooms in on the area I actually dragged over (e.g. not the area that is drawn).
JavaScript console.log stmts output coordinates from the moment I click on the map, but nothing is drawn in the beginning of the drag.
Has anyone had similar problems with WebView? As I said, the code works like a charm in all other browsers I have tried (Safari, Firefox, Chrome, IE). I have looked around on the internet, but I haven't been able to find an answer to my problem.
Code taken from BoxHandler.js:
startBox: function (xy) {
;;;console.log(xy);
;;;console.log("startbox xy=" + xy.x + "," + xy.y);
if(this.zoomBox){
this.removeBox();
}
this.zoomBox = OpenLayers.Util.createDiv('zoomBox',
this.dragHandler.start);
this.zoomBox.className = this.boxDivClassName;
this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
this.map.viewPortDiv.appendChild(this.zoomBox);
OpenLayers.Element.addClass(
this.map.viewPortDiv, "olDrawBox"
);
},
/**
* Method: moveBox
*/
moveBox: function (xy) {
var startX = this.dragHandler.start.x;
var startY = this.dragHandler.start.y;
;;;console.log("dragHandler.start.x=" + this.dragHandler.start.x);
;;;console.log("dragHandler.start.y=" + this.dragHandler.start.y);
var deltaX = Math.abs(startX - xy.x);
var deltaY = Math.abs(startY - xy.y);
this.zoomBox.style.width = Math.max(1, deltaX) + "px";
this.zoomBox.style.height = Math.max(1, deltaY) + "px";
this.zoomBox.style.left = xy.x < startX ? xy.x+"px" : startX+"px";
this.zoomBox.style.top = xy.y < startY ? xy.y+"px" : startY+"px";
console.log("zoombox width=" + this.zoomBox.style.width);
console.log("zoombox height=" + this.zoomBox.style.height);
console.log("zoombox left=" + this.zoomBox.style.left);
console.log("zoombox top=" + this.zoomBox.style.top);
// depending on the box model, modify width and height to take borders
// of the box into account
var box = this.getBoxCharacteristics();
;;;console.log("movebox xOffset=" + box.xOffset);
;;;console.log("movebox yOffset=" + box.yOffset);
if (box.newBoxModel) {
if (xy.x > startX) {
this.zoomBox.style.width =
Math.max(1, deltaX - box.xOffset) + "px";
}
if (xy.y > startY) {
this.zoomBox.style.height =
Math.max(1, deltaY - box.yOffset) + "px";
}
}
},
/**
* Method: endBox
*/
endBox: function(end) {
var result;
console.log(this.map.viewPortDiv.lastElementChild.style);
if (Math.abs(this.dragHandler.start.x - end.x) > 5 ||
Math.abs(this.dragHandler.start.y - end.y) > 5) {
var start = this.dragHandler.start;
var top = Math.min(start.y, end.y);
var bottom = Math.max(start.y, end.y);
var left = Math.min(start.x, end.x);
var right = Math.max(start.x, end.x);
result = new OpenLayers.Bounds(left, bottom, right, top);
} else {
result = this.dragHandler.start.clone(); // i.e. OL.Pixel
}
this.removeBox();
this.callback("done", [result]);
}
Also, I don't know if this is relevant, but when I inspect the HTML div element that holds the map (using Firebug Lite) it looks like the top border of the div is further down than it should be. The map (in correct position on the webpage) is extending beyond the top border. This is different behavior than in the other browsers I mentioned.
Any help would be appreciated.
The extension I'm talking about is the Raphael-zpd: http://pohjoisespoo.net84.net/src/raphael-zpd.js
/* EDIT The script is added to a Raphael document with this command var zpd = new RaphaelZPD(paper, { zoom: true, pan: true, drag: false}); where paper is your canvas */
The script was originally released at the authors github http://www.github.com/somnidea which no longer exists.
What I wanted to do was run the mousewheel zoom out to the threshold as soon as the raphael is loaded. The zoomthreshold is set at the beginning of the script zoomThreshold: [-37, 20]. In the mousewheel scroll function it is compared to zoomCurrent which is by default 0 me.zoomCurrent = 0;
This is the whole mousewheel event part
me.handleMouseWheel = function(evt) {
if (!me.opts.zoom) return;
if (evt.preventDefault)
evt.preventDefault();
evt.returnValue = false;
var svgDoc = evt.target.ownerDocument;
var delta;
if (evt.wheelDelta)
delta = evt.wheelDelta / 3600; // Chrome/Safari
else
delta = evt.detail / -90; // Mozilla
if (delta > 0) {
if (me.opts.zoomThreshold)
if (me.opts.zoomThreshold[1] <= me.zoomCurrent) return;
me.zoomCurrent++;
} else {
if (me.opts.zoomThreshold)
if (me.opts.zoomThreshold[0] >= me.zoomCurrent) return;
me.zoomCurrent--;
}
var z = 1 + delta; // Zoom factor: 0.9/1.1
var g = svgDoc.getElementById("viewport"+me.id);
var p = me.getEventPoint(evt);
p = p.matrixTransform(g.getCTM().inverse());
// Compute new scale matrix in current mouse position
var k = me.root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);
me.setCTM(g, g.getCTM().multiply(k));
if (!me.stateTf)
me.stateTf = g.getCTM().inverse();
me.stateTf = me.stateTf.multiply(k.inverse());
}
The reason I can't just draw a smaller SVG to begin with is that I'm using raster images as the background and need them to be higher resolution. I would still like to start at the furthest point I've set at the threshold. Is it possible for me to somehow use this script to do this? I'm naturally using it otherwise to handle mouse zoom/pan.
//EDIT
There is also this function at the end of the script, but so far I've been unable to work it.
Raphael.fn.ZPDPanTo = function(x, y) {
var me = this;
if (me.gelem.getCTM() == null) {
alert('failed');
return null;
}
var stateTf = me.gelem.getCTM().inverse();
var svg = document.getElementsByTagName("svg")[0];
if (!svg.createSVGPoint) alert("no svg");
var p = svg.createSVGPoint();
p.x = x;
p.y = y;
p = p.matrixTransform(stateTf);
var element = me.gelem;
var matrix = stateTf.inverse().translate(p.x, p.y);
var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
element.setAttribute("transform", s);
return me;
}
Seems like it's used for panning through the document through say click events so that a click would execute the function with the given coordinates. However, as said I've been unable to work it. I don't know how it's supposed to function. I tried paper.ZPDPanTo(100, 100); as well as just ZPDPanTo(100,100) but nothing happens.
You may also want to check out the working branch for Raphaël 2.0, which supposedly adds support for viewBox and transforms, see https://github.com/DmitryBaranovskiy/raphael/tree/2.0.
This doesn't answer your question fully, but it seems quite possible that Raphaël 2.0 will address your use-case.
If you're using pure svg then you can manipulate the zoom&pan positions via the SVG DOM properties currentTranslate and currentScale, see this example.
An example using RAPHAEL ZPD:
var paper = Raphael("container",800,760);
window.paper = paper;
zpd = new RaphaelZPD(paper, { zoom: true, pan: true, drag: false });
paper.circle(100,100, 50).attr({fill:randomRGB(),opacity:0.95});
paper.rect(100,100, 250, 300).attr({fill:randomRGB(),opacity:0.65});
paper.circle(200,100, 50).attr({fill:randomRGB(),opacity:0.95});
paper.circle(100,200, 50).attr({fill:randomRGB(),opacity:0.95});
http://jsfiddle.net/4PkRm/1/
I am calling a JS function through the ExternalInterface using Flex which requires the absolute X and Y coordinates to create a pop-up menu. The Flex application is displayed on the center of an HTML page, therefore there is an HTML X and Y offset to consider.
I have tried using the LocalToGlobal and ContentToGlobal functions, but these are just giving me the X and Y coordinates relative to the Flex application, it is not considering the HTML X and Y offset of having the Flex app in the center of the page or varying different screen resolutions.
Is the best approach to retrieve the HTML X and Y offset using JavaScript? Is there a Flex function I can use that provides the absolute X and Y coordinates based on the HTML page?
Thanks!
If I am understanding you correctly, it sounds like:
You have a small Flex App in the center of an HTML page
Upon some event, you want to create an HTML popup (new browser popup window).
That popup should be centered within the HTML page.
If that's correct, you don't need to use localToGlobal or globalToLocal; you're just looking for the browser viewport bounds. Here is a method I am currently using to place items in relation to the browser bounds (all of this is javascript):
function getBrowserBounds()
{
var size = [0, 0];
if (typeof window.innerWidth != "undefined") {
size = [window.innerWidth, window.innerHeight];
}
else if (typeof document.documentElement != "undefined" && typeof document.documentElement.clientWidth != "undefined" && document.documentElement.clientWidth != 0) {
size = [document.documentElement.clientWidth, document.documentElement.clientHeight];
}
else {
size = [document.getElementsByTagName("body")[0].clientWidth, document.getElementsByTagName("body")[0].clientHeight];
}
var bounds = null;
if (navigator.userAgent.indexOf("MSIE") != -1) // Internet Explorer
bounds = [window.screenLeft, window.screenTop, size[0], size[1]];
else
bounds = [window.screenX, window.screenY, size[0], size[1]];
var width = bounds[0] + (bounds[2]/2);
var height = bounds[1] + (bounds[3]/2);
return bounds;
}
That returns the bounds of the browser's viewport. From there, you can create a popup that is centered within the browser, wherever the browser is within the laptop/desktop screen bounds, using this:
function centerPopup(windowHeight, windowWidth, windowName, windowUri)
{
var bounds = getBrowserBounds();
var centerWidth = bounds[0] + ((bounds[2] - windowWidth) / 2);
var centerHeight = bounds[1] + ((bounds[3] - windowHeight) / 2);
newWindow = window.open(windowUri, windowName, 'resizable=0,width=' + windowWidth +
',height=' + windowHeight +
',left=' + centerWidth +
',top=' + centerHeight);
newWindow.focus();
return newWindow.name;
}
Let me know if that works.
Best,
Lance
Scenario:
The user has two monitors.
Their browser is open on the secondary monitor.
They click a link in the browser which calls window.open() with a specific top and left window offset.
The popup window always opens on their primary monitor.
Is there any way in JavaScript to get the popup window to open on the same monitor as the initial browser window (the opener)?
You can't specify the monitor, but you can specify the position of the popup window as being relative to the where the click caused the window to popup.
Use the getMouseXY() function to get values to pass as the left and top args to the window.open() method. (the left and top args only work with V3 and up browsers).
window.open docs:
http://www.javascripter.net/faq/openinga.htm
function getMouseXY( e ) {
if ( event.clientX ) { // Grab the x-y pos.s if browser is IE.
CurrentLeft = event.clientX + document.body.scrollLeft;
CurrentTop = event.clientY + document.body.scrollTop;
}
else { // Grab the x-y pos.s if browser isn't IE.
CurrentLeft = e.pageX;
CurrentTop = e.pageY;
}
if ( CurrentLeft < 0 ) { CurrentLeft = 0; };
if ( CurrentTop < 0 ) { CurrentTop = 0; };
return true;
}
Here is something I shamelessly reverse engineered from the Facebook oauth API. Tested on a primary and secondary monitor in Firefox/Chrome.
function popup_params(width, height) {
var a = typeof window.screenX != 'undefined' ? window.screenX : window.screenLeft;
var i = typeof window.screenY != 'undefined' ? window.screenY : window.screenTop;
var g = typeof window.outerWidth!='undefined' ? window.outerWidth : document.documentElement.clientWidth;
var f = typeof window.outerHeight != 'undefined' ? window.outerHeight: (document.documentElement.clientHeight - 22);
var h = (a < 0) ? window.screen.width + a : a;
var left = parseInt(h + ((g - width) / 2), 10);
var top = parseInt(i + ((f-height) / 2.5), 10);
return 'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top + ',scrollbars=1';
}
window.open(url, "window name", "location=1,toolbar=0," + popup_params(modal_width, modal_height));
// Pops a window relative to the current window position
function popup(url, winName, xOffset, yOffset) {
var x = (window.screenX || window.screenLeft || 0) + (xOffset || 0);
var y = (window.screenY || window.screenTop || 0) + (yOffset || 0);
return window.open(url, winName, 'top=' +y+ ',left=' +x))
}
Call it like the following and it will open on top of the current window
popup('http://www.google.com', 'my-win');
Or make it slightly offset
popup('http://www.google.com', 'my-win', 30, 30);
The point is that window.screenX/screenLeft give you the position in relationship to the entire desktop, not just the monitor.
window.screen.left would be the ideal candidate to give you the information you need. The problem is that it's set when the page is loaded and the user could move the window to the other monitor.
More research
A final solution to this problem (beyond just offsetting from the current window position) requires knowing the dimensions of the screen that the window is in. Since the screen object doesn't update as the user moves a window around, we need to craft our own way of detecting the current screen resolution. Here's what I came up with
/**
* Finds the screen element for the monitor that the browser window is currently in.
* This is required because window.screen is the screen that the page was originally
* loaded in. This method works even after the window has been moved across monitors.
*
* #param {function} cb The function that will be called (asynchronously) once the screen
* object has been discovered. It will be passed a single argument, the screen object.
*/
function getScreenProps (cb) {
if (!window.frames.testiframe) {
var iframeEl = document.createElement('iframe');
iframeEl.name = 'testiframe';
iframeEl.src = "about:blank";
iframeEl.id = 'iframe-test'
document.body.appendChild(iframeEl);
}
// Callback when the iframe finishes reloading, it will have the
// correct screen object
document.getElementById('iframe-test').onload = function() {
cb( window.frames.testiframe.screen );
delete document.getElementById('iframe-test').onload;
};
// reload the iframe so that the screen object is reloaded
window.frames.testiframe.location.reload();
};
So if you wanted to always open the window at the top left of whatever monitor the window is in, you could use the following:
function openAtTopLeftOfSameMonitor(url, winName) {
getScreenProps(function(scr){
window.open(url, winName, 'top=0,left=' + scr.left);
})
}
Open centered window on current monitor, working also with Chrome:
function popupOnCurrentScreenCenter(url, title, w, h) {
var dualScreenLeft = typeof window.screenLeft !== "undefined" ? window.screenLeft : screen.left;
var dualScreenTop = typeof window.screenTop !== "undefined" ? window.screenTop : screen.top;
var width = window.innerWidth ? window.innerWidth :
document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
var height = window.innerHeight ? window.innerHeight :
document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;
var left = ((width / 2) - (w / 2)) + dualScreenLeft;
var top = ((height / 2) - (h / 2)) + dualScreenTop;
var newWindow =
window.open(url, title, 'scrollbars=yes, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);
// Puts focus on the newWindow
if (window.focus) {
newWindow.focus();
}
}
If you know the resolution of each monitor, you could estimate this.
A bad idea for a public website, but might be useful if you know (for some odd reason) that this scenario will always apply.
Relative position to the mouse (as said above) or to the original browser window could also be useful, Though you'd have to suppose the user uses the browser maximized (which is not necessarily true).
I ran into this issue recently and finally found a way to position the pop up window on the screen that it's triggered from. Take a look at my solution on my github page here: https://github.com/svignara/windowPopUp
The trick is in using the window.screen object, which returns availWidth, availHeight, availLeft and availTop values (as well as width and height). For a complete list of the variables in the object and what these variables represent look at https://developer.mozilla.org/en-US/docs/DOM/window.screen.
Essentially, my solution finds the values of the window.screen whenever the trigger for the popup is clicked. This way I know for sure which monitor screen it's being clicked from. The availLeft value takes care of the rest. Here's how:
Basically if the first available pixel from the left (availLeft) is negative, that's telling us there is a monitor to the left of the "main" monitor. Likewise, if the first available pixel from left is greater than 0, this means one of 2 things:
The monitor is to the right of the "main" monitor, OR
There is some "junk" on the left side of the screen (possibly the application dock or windows start menu)
In either case you want the offset of your popup to start from after the available pixel from the left.
offsetLeft = availableLeft + ( (availableWidth - modalWidth) / 2 )
Only user11153's version works with Chrome and dual screen. Here is its TypeScript version.
popupOnCurrentScreenCenter(url: string, title: string, w: number, h: number): Window|null {
var dualScreenLeft = typeof window.screenLeft !== "undefined" ? window.screenLeft : (<any>screen).left;
var dualScreenTop = typeof window.screenTop !== "undefined" ? window.screenTop : (<any>screen).top;
var width = window.innerWidth ? window.innerWidth :
document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
var height = window.innerHeight ? window.innerHeight :
document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;
var left = ((width / 2) - (w / 2)) + dualScreenLeft;
var top = ((height / 2) - (h / 2)) + dualScreenTop;
var newWindow =
window.open(url, title, 'scrollbars=yes, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);
// Puts focus on the newWindow
if (window.focus && newWindow) {
newWindow.focus();
}
return newWindow;
}
as long as you know the x and y position that falls on the particular monitor you can do:
var x = 0;
var y = 0;
var myWin = window.open(''+self.location,'mywin','left='+x+',top='+y+',width=500,height=500,toolbar=1,resizable=0');