I need to develop a switch statement to run a method when a mouse event has dragged a certain fraction of a page (similar to breakpoints on a page). For example, when a user clicks on an item and drags it 1/12 the width of their screen, I need to run a function once, but if they keep dragging to 2/12 of the screen width, then I need to run the function again.
I created the following switch statement...it works, but I have to copy paste the case statement 12 times to account for a case where the user would drag all the way from left to right. I chose 12, because my page layout uses css grid with 12 columns as the layout:
// gridState is used to keep track of which state we are in since we only want
// to trigger the function `resizeDragColumns()` a single time, when we transition to a new breakpoint
let gridState = 0
switch(true){
// 0.083 = width ratio of a single column in a 12 column grid (e.g. 1/12 = 0.083)
case Math.abs(percentDragged) < (0.083 * 1):
if (gridState != 1) {
this.resizeDragColumns(...)
gridState = 1;
}
break;
case Math.abs(percentDragged) < (0.083 * 2):
if (gridState != 2) {
this.resizeDragColumns(...)
gridState = 2;
}
break;
....
// there are 10 more switch statements just like this
// notice how the only thing that changes is the gridState
// variable and the comparator in the case statement (e.g. 0.083 * 1, 0.083 * 2 ..)
}
This switch statement is contained within an Event observable that is tracking the mouse drag distance and converting it to the percent distance the user has dragged the cursor across the page.
So for example, as the user is dragging, percentDragged is calculated like so:
// point.x = current X position of cursor
// mouseDownCursorPosition = X position of the cursor when the mousedown event was fired (e.g. starting x position)
// rowRef = the container the mouse is dragging within
const percentDragged = (point.x - mouseDownCursorPosition) / rowRef.getBoundingClientRect().width;
IN SUMMARY
I want to dynamically create a switch statement that will trigger a function A SINGLE TIME when the user drags their cursor into certain "breakpoints" on the page.
Is there a simpler way to accomplish this task? This would also be helpful to have since the user can change the grid size to anything they want (8 columns instead of 12), so the switch statement should only have 8 cases instead of 12. Thanks!
Don't use switch in this case, it's quite verbose and error-prone. Instead, use math to identify the (0.083 * NUM) factor:
const draggedColumns = Math.ceil(Math.abs(percentDragged) / 0.083);
if (gridState !== draggedColumns) {
this.resizeDragColumns(...)
gridState = draggedColumns;
}
Kind of an opinion-based one, but here's how I'd do it. I don't think a switch is the right way, because is meant to be used with static and fixed number of cases.
let colDragged = Math.ceil(Math.abs(percentDragged) / 0.083);
if (gridState != colDragged ) {
this.resizeDragColumns(...)
gridState = colDragged ;
}
Related
Is there a way to detect an empty area, without text or images within a web page, using JavaScript?
More precisely, how to determine whether point [x,y] is within a blank area, like in the following example (marked in red)
EDIT: I want to make my question clearer, I'm building an extension which supposed to mark search results as trustworthy or as spam, I want to put my marking at the end of the text of a result item URL.
I also want to do it in a generic way, so it wouldn't work only in Google web page. an example is shown below:
You can test for genuine white space like this :
function isWhiteSpace(coords) {
var element = document.elementFromPoint(coords.x, coords.y);
var whitespace = $(document).add("body, html");
return (whitespace.get().indexOf(element) > -1) ? true : false;
}
where coords is an object with .x and .y properties.
DEMO
document.elementFromPoint(), documented here, is "an experimental technology", so I wouldn't trust my life to it. Test thoroughly on all target platforms.
Edit
For the full detection of all the white you seek, isWhiteSpace() would be the first of two stages. The second stage would be isVisualWhiteSpace() implemented with #remdevtec's approach for example.
As my isWhiteSpace(coords) is inexpensive, you would perform it first and only if it returned false go for the expensive test. You could use the protective property of ||
var isWhite = isWhiteSpace(coords) || isVisualWhiteSpace(coords);
But your real problem will be writing isVisualWhiteSpace(). I can't help with that.
One approach would be to work with a screenshot of the window.
You can use libraries like html2canvas to load a screenshot to a HTML canvas element.
Next, on window.onclick, use the automatic event parameter to get an RGBA array of the clicked coordinate:
var pixelData = canvas.getContext('2d').getImageData(
event.offsetX,
event.offsetY, 1, 1)
.data;
Now if all (or at least the first three) pixelData's items equal 255, it means that this point is white.
if (pixelData[0] == 255 && pixelData[1] == 255 && pixelData[2] == 255) {
// the clicked point is white,
// and if the background-color is white,
// it can be considered an empty point
}
Of course, the down side is that you have to know the background color of the site you're testing, or the background color of the element you click in.
You can build a matrix with width and length of the page.
Set all matrix cells to zero.
Get all elements of the DOM.
Get x, y, width, and height of each element, this link may help
Retrieve the position (X,Y) of an HTML element
Draw the elements in the matrix
for(k=0;k < dom_elements.length;k++) {
for(i=dom_elements[k].y;i < dom_elements[k].length;i++) {
for(j=dom_elements[k].x;j < dom_elements[k].width;j++) {
matrix[i][j] = 1 ;
}
}
}
And finally check if matrix[i][j] is set to zero or 1
I've hit a snag during a project I've been working on for quite some time. The client wants a spinnable dial on his frontpage, like a safe combination dial or a volume knob.
The knob has 4 endvalues, to which the object snaps (to the nearest value). The problem I've hit is that I have no idea how to make JS toggle a hide/show on a certain element once its corresponding value has been reached.
For instance, at 90 degrees rotation, I want element X to show. At 180 degrees rotation, I want element X to hide and Y to take its place/to show. Repeat in 90 degree increments for Z and A.
My current code attempt (amongst many) looks like this
var dialarray = [0,90,180,270];
var rotationSnap = 90;
Draggable.create("#Stage_Group", {
type:"rotation", //instead of "x,y" or "top,left", we can simply do "rotation" to make the object spinnable!
throwProps:true, //enables kinetic-based flicking (continuation of movement, decelerating after releasing the mouse/finger)
snap:function(endValue) {
//this function gets called when the mouse/finger is released and it plots where rotation should normally end and we can alter that value and return a new one instead. This gives us an easy way to apply custom snapping behavior with any logic we want. In this case, just make sure the end value snaps to 90-degree increments but only when the "snap" checkbox is selected.
return Math.round(endValue / rotationSnap) * rotationSnap;
if (rotation("#Stage_Group") = 90) {
sym.$("brand_awareness").show();
} else {
sym.$("brand_awareness").hide();
}
}
})
I'm using Greensocks, where support has been lacking to get me the right answer, so with some help from a more code-savvy friend (who didn't figure it out either) I turn to StackOverflow.
Can somebody please guide me to the solution here? It's driving me bonkers, even if it means ditching the current code for a working code will do, maybe I overlooked another, more logical solution towards making a rotatable object with triggers at certain values?
Thanks in advance!
This would be the code inside your snap. Updated seeing your comment
var imgRotation = Math.round(endValue / rotationSnap) * rotationSnap;
if (imgRotation == 90) {
sym.$("id1").show();
} else if (imgRotation == 180) {
sym.$("id2").show();
} else if (imgRotation == 270 ) {
sym.$("id3").show();
}
else if ((imgRotation >= 360) || (imgRotation == 0)){
sym.$("id4").show();
}
return imgRotation;
In my use case, we're allowing a user to define "zones" (polygons) on a map. The basic polygon editing functionality, enabled by just setting editable: true, works well. However, I need some additional functionality.
For example, when the user starts dragging a vertex, I want to highlight nearby vertices on other polygons, and if the user drags over one of them, it will "snap" the lat/lng of the vertex they were dragging to be identical to the vertex the dragged over.
Has anyone successfully inserted some "extra" code into the editing process? Are there any intermediate events being fired on those vertex handles (while dragging, mouse moving, etc.) that I can hook into, interpret, and draw some extra things on the map? What I'm hoping for is someone who can tell me "Oh, if polygon.obfuscatedVariable is set, those are drag handles, and you can listen for mousemove on polygon.obfuscatedVariable[3], retrieve the lat/long, etc."
Hacks and jury-rigged solutions are acceptable: since the built-in editing is so close to what I want, I really don't feel like recreating it from scratch.
I'd since forgotten about this question, but here's our solution to the problem. Hopefully it's helpful!
Short version:
Whenever a mousedown occurs on a shape, check if it's over a vertex, and if it is, use elementFromPoint to save a reference to the actual HTML <div> representing the vertex handle. In subsequent mousemove and mouseup events, you can poll the screen position of this <div> and compare it to the locations of other points on the map.
Long version:
I've yanked out the relevant functions from our application, so you'll need to ignore some of our specific functions and objects etc., but this does show our working "snap-to-point" implementation.
First off, we will be doing pixel-to-lat-lng conversions, so you'll need a simple Overlay defined somewhere (you always need one to do these calculations):
_this.editor.overlay = new g.maps.OverlayView();
_this.editor.overlay.draw = function () {};
_this.editor.overlay.setMap(This.map);
Now, anytime we've initialized a shape on the map, we add a mousedown event handler to it.
g.maps.event.addListener(_this.shape, 'mousedown', function (event) {
if (event.vertex >= 0) {
var pixel = _this.editor.overlay.getProjection().fromLatLngToContainerPixel(_this.shape.getPath().getAt(event.vertex));
var offset = _this.mapElement.offset();
var handle = document.elementFromPoint(pixel.x + offset.left, pixel.y + offset.top);
if (handle) {
_this.dragHandle = $(handle);
_this.snappablePoints = _this.editor.snappablePoints(_this);
} else {
_this.dragHandle = null;
_this.snappablePoints = null;
}
}
});
(You'll notice the call to snappablePoints, which is just an internal utility function that collects all the points that would be valid for this point to snap to. We do it here because it would be an expensive loop to perform on every single mousemove.)
Now, in our shape's mousemove listener, because we've saved a reference to that <div>, we can poll its screen position and compare it to the screen position of other points on the map. If a point is within a certain pixel range (I think ours is 8 pixels), we save it and hover a little icon telling the user we're going to snap.
g.maps.event.addListener(this.shape, 'mousemove', function (event) {
var projection, pixel, pixel2, offset, dist, i;
if (event.vertex >= 0 && this.dragHandle) {
// If dragHandle is set and we're moving over a vertex, we must be dragging an
// editable polygon point.
offset = this.editor.mapElement.offset();
pixel = {
x: this.dragHandle.offset().left - offset.left + this.dragHandle.width() / 2,
y: this.dragHandle.offset().top - offset.top + this.dragHandle.height() / 2
};
// Search through all previously saved snappable points, looking for one within the snap radius.
projection = this.editor.overlay.getProjection();
this.snapToPoint = null;
for(i = 0; i < this.snappablePoints.length; i++) {
pixel2 = projection.fromLatLngToContainerPixel(this.snappablePoints[i]);
dist = (pixel.x - pixel2.x) * (pixel.x - pixel2.x) + (pixel.y - pixel2.y) * (pixel.y - pixel2.y);
if (dist <= SNAP_RADIUS) {
this.snapToPoint = this.snappablePoints[i];
$('#zone-editor #snapping').css('left', pixel.x + 10 + offset.left).css('top', pixel.y - 12 + offset.top).show();
break;
}
}
if (!this.snapToPoint) {
$('#zone-editor #snapping').hide();
}
});
A little cleanup when the user stops moving the mouse:
g.maps.event.addListener(this.shape, 'mouseup', function (event) {
// Immediately clear dragHandle, so that everybody knows we aren't dragging any more.
// We'll let the path updated event deal with any actual snapping or point saving.
_this.dragHandle = null;
$('#zone-editor #snapping').hide();
});
Last, we actually handle the "snapping", which is really just a tiny bit of logic in an event listener on the shape's path.
g.maps.event.addListener(this.shape.getPath(), 'set_at', function (index, element) {
if (this.snapToPoint) {
// The updated point was dragged by the user, and we have a snap-to point.
// Overwrite the recently saved point and let another path update trigger.
var point = this.snapToPoint;
this.snapToPoint = null;
this.shape.getPath().setAt(index, point);
} else {
// Update our internal list of points and hit the server
this.refreshPoints();
this.save();
};
// Clear any junk variables whenever the path is updated
this.dragHandle = null;
this.snapToPoint = null;
this.snappablePoints = null;
});
Fin.
I'm working on a Javascript/jQuery calendar which includes a month view and a day view. Clicking the days will change the date, which will update the date variables in the day view.
The day view is split up into half hour segments from midnight to 11:00 PM. Clicking on any half hour <tr> (the day view is a table) will create an event between that time clicked and an hour in the future, as well as append a div on top of the calendar, spanning the range of time and positioned at the correct starting point (each pixel is a minute...)
There is a problem, however. If you create an "event" between a certain time span where there is already one in place, they overlap. This is the default behavior, obviously, but what I would like to happen is that if an event is created between a range of dates that is already occupied by an event, they align side by side so that they're not overlapping.
This resembles the behavior seen in the iCal app for mac:
Now my first thought to achieve such a goal was to use collision detection, but all the jQuery plugins for this are bloated or require the elements to be draggable.
Then I thought there might be a way in CSS to do this, where if two elements are overlapping, they split the width evenly.
Then I thought that's ridiculously far fetched, so I'm wondering how I can achieve this as easily as possible.
I'll post the full code in a jsFiddle, but for the most important function would be insertEvent which looks like this:
function insertEvent(start, end){
var end_minutes = new Date(end).getMinutes();
var end_border = new Date(new Date(end).setMinutes(end_minutes + 2));
//$(".day_date").html(start + "<br />" + end);
var diff = Math.abs(end_border - new Date(start));
var minutes = Math.floor((diff/1000)/60);
var start_element = $("td").find("[data-date='" + start + "']");
var offset = start_element.offset().top - $(".second").offset().top;
var this_element = $("<div class='event' style='height:" + minutes + "px;margin-top:" + offset + "px;'></div>");
$(".right").prepend(this_element);
}
This takes two parameters in the javascript new Date() format, one for the start date and one for the end date.
The fiddle: http://jsfiddle.net/charlescarver/HwdwL/
One of the the problems I see with your approach is that there isn't a structure to the storage of the data. I've built a calendar in Javascript before and it's not easy work. First, make sure you have some kind of abstraction for the calendar event. Something like:
function CalendarEvent(startDateTime, endDateTime) {
this.startDateTime = startDateTime;
this.endDateTime = endDateTime;
}
CalendarEvent.prototype.start = function() {
return this.startDateTime.getTime();
};
CalendarEvent.prototype.end = function() {
return this.endDateTime.getTime();
};
CalendarEvent.new = function(startDateTime, endDateTime) {
// This is a little factory method. It prevents calendar events
// from having end times that fall before the start time.
// USE THIS TO INSTANTIATE A NEW CALENDAR EVENT
if(endDateTime.getTime() < startDateTime.getTime()) {
throw new Error("End time falls before start time");
}
return new CalendarEvent(startDateTime, endDateTime);
};
CalendarEvent.compare = function(eventOne, eventTwo) {
// this is a class method to compare two events
// If used with sort it will sort by startDateTime
return eventOne.start() - eventTwo.start();
};
// ... add any other methods you need
Next you're going to want to sort the calendar events. I would sort by start time. Then once it is sorted you can actually re-render everything when changes are made. As long as you sort correctly, determining if a calendar event collides is as simple as this:
CalendarEvent.prototype.intersects = function(otherEvent) {
// If the other event starts after this one ends
// then they don't intersect
if(otherEvent.start() > this.end()) {
return false;
}
// If the other event ends before this one starts
// then they don't intersect
if(otherEvent.end() < this.start()) {
return false;
}
// Everything else is true
return true;
};
Because the data is sorted you know that if two or more calendar events intersect they will have to share the space. Granted, you must think about a few things when you divide the space. Do you want a naive implementation where you just share the space equally from left to right (left having the earliest start time). If so your visual representation could look like this if it had 4 events that shared a space (each block is an event):
However if your events have strange shapes they might cause your calendar to look strange. Consider the following:
In this instance event 2 takes up a lot of vertical space and all the space underneath event 1 is unused. Maybe for a better UX you don't want that kind of thing to happen. If so you should design your rendering algorithm accordingly. Just remember that it is probably easiest to re-render on every change that you encounter, but it's all about how you store the data. If you do not store the data in some kind of structure that is easily traversed then you won't be able to do this kind of thing.
But to complete the answer to your question, here is a fairly naive example. I haven't tested it so this is a pretty big assumption of it working. It is not entirely complete you will have to edit the rendering for yourself. This is merely to give you an idea of how to get it to work. It could definitely look prettier:
function renderCalendarEvents(calendarEvents) {
// Sort the calendar events (assuming calendarEvents is an array)
var sortedEvents = calendarEvents.sort(CalendarEvent.compare);
var index = 0;
// renderEvents is an anonymous function that will be called every time
// you need to render an event
// it returns it's columnDivisor.
var renderEvent = function(position) {
var currentEvent = sortedEvents[index];
var nextEvent = sortedEvents[index + 1];
// The default column divisor is determined by
// the current x-position + 1
var columnDivisor = position + 1;
// Increment before any recursion
index += 1;
// Check if nextEvent even exists
if(nextEvent) {
// If the nextEvent intersects with the current event
// then recurse
if(currentEvent.intersects(nextEvent)) {
// We need to tell the next event that it starts at the
// column position that is immediately +1 to the current event
columnDivisor = renderEvent(position + 1);
}
}
// placeEvent() is some function you can call to actually place
// the calendar event element on the page
// The position is the x-position of the current event
// The columnDivisor is a count of the amount of events sharing this column
placeEvent(currentEvent, position, columnDivisor);
return columnDivisor;
};
while(true) {
// render events until we're done
renderEvent(0);
if(index >= sortedEvents.length) {
break;
}
}
}
Essentially the idea with this particular algorithm is that if the nextEvent on the list exists and that event intersects with the currentEvent then we need to split the width of the currentEvent. It keeps on recursing until it finds no more intersections then it makes it's way back up the chain of recursive calls. I skipped the actual DOM manipulation logic because really the hard part is determining how much you need to split the actual column in order to get these events to fit. So hopefully this all makes a little bit of sense.
EDIT:
To be much more clear, in order to add this to your existing code I would replace your insertEvent function with something like this. I don't write all of the logic for you so you'll have to do some of your own writing. But that's half the fun :-).
function insertEvent(start, end) {
var newEvent = Calendar.new(start, end);
// you'll have to store the array somewhere.
// i'm just assuming some kind of global right now
eventsArray.push(newEvent);
// You'll want to destroy any event elements
destroyCurrentEventElements();
// Now run the rendering function
renderCalendarEvents(eventsArray);
}
http://davzy.com/gameA/
I can't figure out a smart way to get gravity. Now with this it detects which block the character is over but it does't drop to that block!
Is there a better way to do gravity? I'd like to do this without a game library.
I don't know what you mean by "get gravity"; your question is unclear. I assume that if you can detect when the block is over, you can use the following formula:
s(t) = ut + 1/2at2
Where s is the distance at time t, u is the initial velocity (which in your case would be zero), and a is the acceleration (on Earth this is 9.8m/s2). Essentially you would be adjusting the top position of your object based on the value you get at time t (so original top position of object + s(t)). I would imagine you would use some sort of animation loop. Perhaps a setInterval. Maybe others with more experience in Javascript animation can chime in about the best way to implement this. However, this would be the formula that you would be using to figure out where the object is at time t, if it falls.
Basically gravity in a platformer goes like this:
var currentGrav = 0.0;
var gravAdd = 0.5; // add this every iteration of the game loop to currentGrav
var maxGrav = 4.0; // this caps currentGrav
var charPosY = getCharPosY(); // vertical position of the character, in this case the lower end
var colPosY = getColDow(); // some way to get the vertical position of the next "collision"
for(var i = 0; i < Math.abs(Math.ceil(currentGrav)); i++) { // make sure we have "full pixel" values
if (charPosY == colPosY) {
onGround = true;
break; // we hit the ground
}
onGround = false;
charPosY++;
}
Now to jump one could simply do this:
if (jumpKeyPressed && onGround) {
currentGrav = -5.0; //
}
You can, if you want(and understand C), check out my game for a basic platformer(with moving platforms) here:
http://github.com/BonsaiDen/Norum/blob/master/sources/character.c