How to get the coordinates of the point that I click? - css

I am playing with <canvas> tag and just encountered a problem:
https://jsfiddle.net/awguo/6yLqa5hz/
I want to get the coordinates of a point when I click on a canvas.
I searched for a while and found some functions, but as a 300x300 canvas, the point of its right-bottom point is (300,150). Shouldn't it be 300,300 (because the img is 300x300 and the canvas is 100% on it)?
Why?
What should I do to get the 300x300?

You must adjust the event.clientX and event.clientY coordinates returned by your event handlers by the offset of the canvas element vs the window. To do that you can use canvas.getBoundingClientRect to get the left & top canvas offsets. Be sure to listen for resize & scroll events. When those events happen you must re-fetch the canvas's current offset.
// Fetch offsetX & offsetY variables that contain the
// canvas offset versus the window
// Re-fetch when the window is resized or scrolled
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
Here's how to use the offsets to calculate correct mouse coordinates in an event handler:
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// calculate the mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// your stuff
}

function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX,
y: evt.clientY
};
}
Is enough to work. You image is 350px big, not 300px.

Related

How to render a webgl in a responsive iframe?

I'm using ThreeJS to paint 3D experiments of planets in a responsive iframe. Here's an illustration of Earth, for example.
This works fine on DOMContentLoaded, but not onresize. The scene is cut off or remains at (0,0) top-left as per its first render. Note, this is the recommended behavior according to webgl anti patterns.
But I want to scale the planet according to the new size of the iframe, both when the window is resized or when orientation changes (and leads to a resize). Essentially, to make it cross-platform and responsive.
I have a few ideas to make this work:
Use innerWidth/innerHeight, even though it is an anti-pattern, but can work if we dispose() and repaint the scene with new height & width of the iframe.
Ensure the disposition and repaint is carried out efficiently, i.e. setTimeout and detect resizeFinish() like so:
function resizeFinish() {
width = window.clientWidth;
height = window.clientHeight;
renderer.setSize(width, height);
render();
if (scene) {
while (scene.children.length > 0) {
scene.remove(scene.children[scene.children.length - 1]);
}
// cleanup without calling render (data needs to be cleaned up before a new scene can be generated)
renderer.dispose(scene.children);
}
alertSize();
}
window.onresize = function() {
clearTimeout(doit);
doit = setTimeout(function() {
resizeFinish();
}, 1000);
};
function render() {
controls.update();
sphere.rotation.y += 0.003;
clouds.rotation.y += 0.005;
requestAnimationFrame(render);
renderer.render(scene, camera);
}
Is this good enough?

Place objects on a defined shape

I'm looking for a way, to allow a drag and drop, only on what's the area of an image.
So, if the image is the following:
I will have the option to add another layer on top, with some text, but that new layer, can't go anywhere, but inside the shape of that image.
So, after some research, I still wonder how can I achieve something like that?
I know I can use map and area to map existing elements on an image, but how can I add new elements, that fit only to that map? Any ideas?
To illustration your solution, let's assume you want to drop a raindrop in your cloud and be sure all raindrop pixels are fully inside the cloud...
Then the answer to your question requires asking this question:
Are all opaque raindrop pixels fully inside the cloud?
To answer this question, you must compare every pixel on the raindrop with every pixel underneath.
If the raindrop pixel is transparent, then ignore this pixel because this part of the raindrop is transparent anyway.
If the raindrop pixel is opaque and the pixel underneath is transparent, then this raindrop pixel is not contained in the cloud.
If both the raindrop pixel & underneath pixel are opaque, then this raindrop pixel is contained in the cloud.
You can get the required transparency information about the raindrop and cloud by drawing their images on a canvas and then requesting getImageData. 'getImageData' returns the red, green, blue & alpha information about every pixel on the canvas. To answer the question, we just need the alpha information.
Here's annotated code and a Demo:
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var offsetX,offsetY;
// load the cloud and raindrop images
var cloudmap,rainmap;
var rain=new Image();
rain.crossOrigin='anonymous';
rain.onload=start;
rain.src='https://dl.dropboxusercontent.com/u/139992952/raindrop1.png';
var cloud=new Image();
cloud.crossOrigin='anonymous';
cloud.onload=start;
cloud.src="https://dl.dropboxusercontent.com/u/139992952/cloud.png";
var cloud1=new Image();
cloud1.crossOrigin='anonymous';
cloud1.onload=start;
cloud1.src="https://dl.dropboxusercontent.com/u/139992952/multple/cloud1.png";
var imageCount=3;
function start(){
if(--imageCount>0){return;}
// resize the canvas to the size of the cloud
// and draw the cloud on the canvas
cw=canvas.width=cloud.width;
ch=canvas.height=cloud.height;
draw();
// create a transparency map of the cloud
cloudmap={
width:cloud.width,
height:cloud.height,
map:transparencyMap(cloud),
};
// create a transparency map of the raindrop
rainmap={
width:rain.width,
height:rain.height,
map:transparencyMap(rain),
}
// listen for mousemove events
$("#canvas").mousemove(function(e){handleMouseMove(e);});
// listen for window scroll events
calcCanvasOffset();
$(window).scroll(function(){ calcCanvasOffset(); });
}
function transparencyMap(img){
// create a temp canvas sized to the img size
var c=document.createElement('canvas');
var cctx=c.getContext('2d');
c.width=img.width;
c.height=img.height;
// draw the img on the canvas
cctx.drawImage(img,0,0);
// get the pixel data for every pixel on the canvas
var data=cctx.getImageData(0,0,c.width,c.height).data;
// create an array that reports the status
// of every pixel on the canvas
// (status: true if opaque, false if transparent)
var map=[];
for(var i=0;i<data.length;i+=4){
map.push(data[i+3]>250);
}
return(map);
}
function draw(mouseX,mouseY,isContained){
// draw the cloud
ctx.clearRect(0,0,cw,ch);
if(isContained){
// draw the blue cloud indicating the raindrop is not fully contained
ctx.drawImage(cloud,0,0);
}else{
// draw the yellow cloud indicating the raindrop is fully contained
ctx.drawImage(cloud1,0,0);
}
// if the mouse position was supplied
if(mouseX){
ctx.drawImage(rain,mouseX-rain.width/2,mouseY-rain.height/2);
}
}
function AcontainsB(ax,ay,amap,bx,by,bmap){
// set a flag indicating of the raindrop is fully contained in the cloud
var isContained=true;
// calc the relative position of the raindrop vs cloud in the canvas
var deltaX=bx-ax;
var deltaY=by-ay;
// test every pixel of B against A
// if B is opaque and a is not opaque then B is not contained by A
var y=0;
while(isContained && y<bmap.height){
var x=0;
while(isContained && x<bmap.width){
// calc the map array indexes for the cloud(A) & raindrop(B)
var mapIndexA=(y+deltaY)*amap.width+(x+deltaX);
var mapIndexB=y*bmap.width+x;
// if the raindrop is opaque at this pixel
if(bmap.map[mapIndexB]){
// ...and if this pixel is off canvas
if(mapIndexA<0 || mapIndexA>=amap.map.length){
// ...then the raindrop is not in the cloud at this pixel
isContained=false;
// ...or if the pixel under the raindrop is transparent
}else if(!amap.map[mapIndexA]){
// ...then the raindrop is not in the cloud at this pixel
isContained=false;
}
}
x++;
}
y++;
}
return(isContained);
}
function handleMouseMove(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get the current mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// calc the top-left corner of the raindrop image
var rainX=parseInt(mouseX-rain.width/2);
var rainY=parseInt(mouseY-rain.height/2);
// ask if the raindrop is fully contained in the cloud
var isContained=AcontainsB(0,0,cloudmap,rainX,rainY,rainmap);
// redraw the cloud & raindrop
draw(mouseX,mouseY,isContained);
}
// recalc the canvas offsetX & offsetY
function calcCanvasOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Use the mouse to drag the raindrop over the canvas<br>The cloud turns blue if the rain is fully inside the cloud</h4>
<canvas id="canvas" width=600 height=500></canvas>
You can use this "transparency mapping" to test anything you can draw into a canvas including images and text. Note that text is drawn with context.fillText.
If you're dropping a text element external to the canvas (maybe using jqueryUI or native "draggable"), you will have to:
Fetch the x,y position of the drop.
Fetch the text content of the dropped element.
Create a temporary canvas containing the text. Do it like this...
function textToCanvas(text,fontsize,fontface){
var c=document.createElement('canvas');
var cctx=c.getContext('2d');
cctx.font=fontsize+'px '+fontface;
var textWidth=cctx.measureText(text).width;
c.width=textWidth;
c.height=fontsize+4;
cctx.font=fontsize+'px '+fontface;
cctx.textBaseline='top';
cctx.fillText(text,0,0);
return(c);
}
Use the temporary canvas just like an image to create a transparency map. This is possible because the canvas will accept another canvas as its image source.
Good luck with your project.
[ Additional questions from comments ]
Additional Question:
"How will this react when you have a raindrop placed somewhere in the
cloud (saved it's position), and then you try to add something that's
on top of it?" Said another way: "How can I test if 2 objects are
overlapping?"
Answer: You can test if 2 objects overlap by using transparency maps again. Create another test (AintersectsB) which tests if any pixel in A is opaque while the associated pixel in B is also opaque. You can start with the AcontainsB and modify it to create the AintersectsB test.
Additional Question:
"How can I save and later restore dropped object positions?"
Answer: Since canvas doesn't remember what it draws, you must remember for it. This is usually done by creating a javascript object for each dropped item and saving all these objects in an array. That way if you need to save the positions on a server you can use JSON.stringify to turn the array of objects into a string and send that string to the server to be saved in a database (or file). To recreate your work, the server pulls the string from the database and sends it to the browser. The browser uses JSON.parse to turn the string back into an array of javascript objects. Then you can redraw the scene exactly as it was using the information in the objects.

Flex scrolling more than 10.000 pixels

The Flex Canvas container is limited to 10,000x10,000 pixels. Yet, I've seen Flex apps that are scrolling way more than 10,000 pixels. Any idea how this can be done?
The content I want to scroll is in pieces already, but I need to add those pieces to something that can scroll vertically more than 10,000 pixels.
Depending on what you actually want to display you may be able to split your content into tiles. This is how Google Maps works, every time the map is moved the program determines which tiles are visible on the screen and loads them in. Any markers or overlays that are on the map are notified that the map has moved and determine where their new location is. If their location is off the screen they can be removed from the canvas. For example, the width of all the tiles at zoom level 20 on Google Maps is (256 pixels * 2^20) which equals 268,435,456 total pixels.
Essentially you just need to create a special Sprite that keeps track of the actual x,y location it is supposed to be positioned at. Any time the container moves you simply iterate through all of the child objects and determine where to put them.
function onCanvasScroll() {
//determine the top left coordinates of the canvas
//you will figure out how to do this depending on how the scrolling window
//is implemented
var canvas_scroll_x;
var canvas_scroll_y;
//create a bounding box for the canvas
var view_bounds = new Rectangle(canvas_scroll_x, canvas_scroll_y, canvas.width, canvas.height);
for (child in canvas) {
var x = child.actual_x - view_bounds.x;
var y = child.actual_y - view_bounds.y;
var childBounds = new Rectangle(x, y, child.width, child.height);
//determine if the component is visible on screen
if (view_bounds.intersects(child_bounds)) {
child.visible = true;
child.x = x;
child.y = y;
}
else {
child.visible = false;
}
}
}
So if you have a canvas that is positioned at (100, 20000), a sprite that is positioned at (300, 20100), and a window that is (640,408), you would place it at (200, 100) and it would be visible on the screen.
Instead of just setting visible to true or false a better approach would be to remove the items from the canvas entirely when they are not within the bounds of the view.

Spark List scrolling by mouse position

<s:List xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
mouseOver="scroller_mouseOver(event)"
height="308" width="110">
As seen above, I have a Spark List with the MouseOver event:
protected function scroller_mouseOver(e:MouseEvent):void {
CursorManager.removeAllCursors(); // Remove all previous Cursors
if (mouseX > 24 && mouseX < 143) {
if (mouseY > 220) {
CursorManager.setCursor(downCursorSymbol); // Down Cursor
} else if (mouseY < 87) {
CursorManager.setCursor(upCursorSymbol); // Up Cursor
}
// Scroll as its Mouse Overed
this.addEventListener(Event.ENTER_FRAME, scrollViaY);
}
}
private function scrollViaY(e:Event):void {
if (mouseX > 24 && mouseX < 143) {
if (mouseY > 220) {
this.layout.verticalScrollPosition += (mouseY - 220)*0.5;
}
else if (mouseY < 87) {
this.layout.verticalScrollPosition += (mouseY -86)*0.5;
}
}
}
The following picture describes the area I would like to track hovering.
The Problem: When I hover the upper (red) part of the List (I would like to have the List scroll downward, the higher the mouse position is, the faster it should scroll). Similarly, If the mouse hovers over the bottom (red) part, the List scrolls upwards, and the lower the mouse is hovered, the faster the list would scroll. The current code does work - Though, the trace outs make it obvious that:
this.layout.verticalScrollPosition += (mouseY - 220)*0.5;
or
this.layout.verticalScrollPosition += (mouseY -86)*0.5;
...are giving jumping effects, is there a way to make these values change more linearly or smoother?
I created an AnimateProperty, but that works well only if I would like to scroll to a selectedIndex, In this case, I would like the scroller to keep scrolling linearly as the mouse is hovered to a particular red area, and increase in speed when I scroll to either extremity.
The Objective: While the mouse is over the Bottom (red part )of the List, the verticalScrollPosition scrolls faster as it gets farther than the center of the ticket list... yet there is jumping effect going on, I suppose due to the EnterFrame. the Same with Upper Part... the higher the cursor is, the faster the List should scroll.
I feel this is a common problem to Flash CSx developers.
I have implemented something similar, except it was a horizontal scrolling list, and also I used a scroll left/right buttons on the sides of the list, the mouse_over effect is triggered by the buttons, not the list (but this shouldn't be an impediment when adapting the code).
To do the actual scrolling, I used an animate effect which targeted the list's layout, and more specifically, the layout's horizontalScrollPosition. This it what the MXML code looks like:
<s:Animate id="scrollAnimation" target="{listLayout}">
<s:easer>
<s:Linear easeInFraction="0" easeOutFraction="0" />
</s:easer>
<s:SimpleMotionPath id="scrollMotionPath" property="horizontalScrollPosition" />
</s:Animate>
That easer is necessary because animate effect usually have 3 motion stages (acceleration, uniform motion and deceleration), so by default you would get a wavy scrolling motion.
I used RobotLeft for that project, so the view is mediated like this:
When the mediator is registered, add the scroll button listeners:
viewComponent.scrollLeft.addEventListener(MouseEvent.MOUSE_OVER, onScrollLeft);
viewComponent.scrollRight.addEventListener(MouseEvent.MOUSE_OVER, onScrollRight);
viewComponent.scrollLeft.addEventListener(MouseEvent.MOUSE_OUT, onScrollOut);
viewComponent.scrollRight.addEventListener(MouseEvent.MOUSE_OUT, onScrollOut);
We also need to add a listener so we can tell when the scrolling effect finished, because we might need to replay it:
viewComponent.scrollEffect.addEventListener(EffectEvent.EFFECT_END, onScrollEnd);
We can now implement the scrolling method. Since the view is mediated, we have access to the scroll effect, we can reference it here (and also the path). This what the method looks like:
private function scrollOnlineFriendsList(scrollBy:int):void
{
// Set the scrollBy value and play the scrolling effect
viewComponent.scrollMotionPath.valueBy = scrollBy;
viewComponent.scrollEffect.play();
// Store the scrollBy value in case the effect will need to be replayed,
// if the mouse over event is still over the scrolling button/area
this.scrollBy = scrollBy;
canKeepScrolling = true;
}
Finally, the handlers:
private function onScrollLeft(event:MouseEvent):void
{
scrollOnlineFriendsList(-33);
}
private function onScrollRight(event:MouseEvent):void
{
scrollOnlineFriendsList(33);
}
private function onScrollOut(event:MouseEvent):void
{
canKeepScrolling = false;
// If you would like the effect to continue playing
// until it finishes the current scrolling operation,
// once the mouse is no longer over a scroll button,
// just comment this line
viewComponent.scrollEffect.stop();
}
private function onScrollEnd(event:EffectEvent):void
{
// When the scroll effect has finished playing,
// if the mouse is still over the scroll button/area,
// replay the effect
if (canKeepScrolling)
scrollOnlineFriendsList(scrollBy);
}

How to detect mouse moving while left button down

I want to use javascript to detect the mouse position once the mousedown event has fired. It appears I am not getting an onmousemove event when the left button is held down. The cursor is changing to some circle with a cross through it.
My whole goal is to detect the start position of the cursor on mousedown and change the top left style of a image in a div with overflow set (kind of fake pan effect).
Thanks
Set up an ondragstart handler:
function dragstart(ev)
{
if (!ev) ev = window.event;
ev.returnValue = false;
return false;
}
image.ondragstart = dragstart;

Resources