Find relative position of div inside another div (ng-drag-drop) - javascript

I am preparing a web app which allows user to design Identity cards.
I am using ng-drag-drop https://www.npmjs.com/package/ng-drag-drop for the same.
I wish to get co-ordinates of a div box inside a bigger div box.
As per the image, I want (x,y) with respect to outer div
Things to achieve:
place inner div at specific position inside div
drag around -- fetch new co-ordinates
Problem : when I reposition the box programmatically and then move it around, the x,y values are coming negative –
HTML:
<div class="card-boundary" #boundary>
<img width="1039" height="673" [ngStyle]="bgStyle" />
<div cdkDragLockAxis="elem.dragLock" (cdkDragEnded)="dragEnd($event)" (cdkDragStarted)="onElemClick(elem,$event)" cdkDragBoundary=".card-boundary" cdkDrag *ngFor="let elem of elemList" [ngClass]="{'example-box-selected':(selectedElem && elem.field == selectedElem.field)}"
(click)="onElemClick(elem,$event)" class="example-box" style="text-align: start" [ngStyle]="elem" #item>
{{elem.sample}}
</div>
</div>
Typescript:
dragEnd(event: CdkDragEnd) {
const transform = event.source.element.nativeElement.style.transform;
let regex = /translate3d\(\s?(?<x>[-]?\d*)px,\s?(?<y>[-]?\d*)px,\s?(?<z>[-]?\d*)px\)/;
var values = regex.exec(transform);
this.selectedElem.startX = +values[1];
this.selectedElem.startY = +values[2];
}
CSS:
.card-boundary {
width: 1039px;
height: 676px;
max-width: 100%;
position: relative;
background-color: #fff
}
.bg-boundary {
width: 1039px;
height: 676px;
max-width: 100%;
}
.example-box {
cursor: move;
position: relative;
z-index: 1;
}
.example-box-selected {
cursor: move;
background: #eee;
position: relative;
z-index: 1;
}

When the position of an element is changed using css transform, the values of transform indicate the position change from its' original position.
Also when an element is moved using transform, its' original position will never change, as the new position should always be calculated from original position + change in position.
So if you can get the original position of the element, you can add the transform values to the original position values to get the current position.
The below code will give you the current position of the element with respect to its' offsetParent.
dragEnd(event: CdkDragEnd) {
const element = event.source.element.nativeElement;
const transform = element.style.transform;
let regex = /translate3d\(\s?(?<x>[-]?\d*)px,\s?(?<y>[-]?\d*)px,\s?(?<z>[-]?\d*)px\)/;
var values = regex.exec(transform);
this.selectedElem.startX = element.offsetLeft + +values[1];
this.selectedElem.startY = element.offsetTop + +values[2];
}
PS: offsetParent = 'closest positioned parent' || document.body

Related

JavaScript: Detect if DOM element overlaping, inside or ouside with DOM element?

I'm making a small game in JavaScript. I've created a small example js fiddle demo link. There are three situations as listed bellow:
A: Outside of target object.
B: Overlaping with target object's border.
C: Inside of target object.
According to Have object detect if completely inside other object JavaScript, I've known how to detect if the object inside of target (Situation C). How about situation A and B?
<div class="main">
<div class="target"></div>
<div class="obj">A</div>
<div style="top:15%; left:50%;" class="obj">B</div>
<div style="top:25%; left:35%;" class="obj">C</div>
</div>
One way to do this would be to use Element#getBoundingClientRect() to obtain the absolute coordinates of the DOM elements being classified for overlap, containment, etc.
This function returns to top, right, bottom and left coordiates of the corresponding DOM element which you can use to determine containment of an element with respect to a container.
You could implement a function like findContainment() shown below, where the containment of element is classified against a container element:
function findContainment(element, container) {
/*
Obtain the bounding rectangle for each element
*/
const brE = element.getBoundingClientRect()
const brC = container.getBoundingClientRect()
/*
If the boundaries of container pass through the boundaries of
element then classifiy this as an overlap
*/
if (
/* Does container left or right edge pass through element? */
(brE.left < brC.left && brE.right > brC.left) ||
(brE.left < brC.right && brE.right > brC.right) ||
/* Does container top or bottom edge pass through element? */
(brE.top < brC.top && brE.bottom > brC.top) ||
(brE.top < brC.bottom && brE.bottom > brC.bottom)) {
return "overlap";
}
/*
If boundaries of element fully contained inside bounday of
container, classify this as containment of element in container
*/
if (
brE.left >= brC.left &&
brE.top >= brC.top &&
brE.bottom <= brC.bottom &&
brE.right <= brC.right
) {
return "contained"
}
/*
Otherwise, the element is fully outside the container
*/
return "outside"
}
const main = document.querySelector(".main")
console.log("A", findContainment(document.querySelector(".a"), main))
console.log("B", findContainment(document.querySelector(".b"), main))
console.log("C", findContainment(document.querySelector(".c"), main))
console.log("D", findContainment(document.querySelector(".d"), main))
console.log("E", findContainment(document.querySelector(".e"), main))
.main {
width: 50%;
height: 200px;
border: 5px solid #000;
position: relative;
}
.obj {
width: 40px;
height: 40px;
border: 1px solid blue;
position: absolute;
}
<div class="main">
<div style="top:105%; left:25%;" class="obj a">A</div>
<div style="top:15%; left:-5%;" class="obj b">B</div>
<div style="top:20%; left:40%;" class="obj c">C</div>
<div style="top:20%; left:110%;" class="obj d">D</div>
<div style="top:90%; left:95%;" class="obj e">E</div>
</div>
Here's a working fiddle as well - hope that helps!

Finding where element meets top of scrollable div

I have a scrollable div container fits multiple "pages" (or div's) inside of it's container.
My goal is to, at any given moment, figure out where inside my red container does it reach the top of my scrollable container. So it can be a constant on scroll event, or a button that triggers this task.
So for example. If I have a absolute div element inside one of my red boxes at top:50px. And if I scroll to where that div element reaches the top of my scrollable container. The trigger should say that I am at 50px of my red container.
I'm having a hard time grasping how to accomplish this but I've tried things like:
$("#pageContent").scroll(function(e) {
console.log($(this).scrollTop());
});
But it doesn't take into account the separate pages and I don't believe it it completely accurate depending on the scale. Any guidance or help would be greatly appreciated.
Here is my code and a jsfiddle to better support my question.
Note: If necessary, I use scrollspy in my project so I could target which red container needs to be checked.
HTML
<div id="pageContent" class="slide" style="background-color: rgb(241, 242, 247); height: 465px;">
<div id="formBox" style="height: 9248.627450980393px;">
<div class="trimSpace" style="width: 1408px; height: 9248.627450980393px;">
<div id="formScale" style="width: 816px; -webkit-transform: scale(1.7254901960784315); display: block;">
<form action="#" id="XaoQjmc0L51z_form" autocomplete="off">
<div class="formContainer" style="width:816px;height:1056px" id="xzOwqphM4GGR_1">
<div class="formContent">
<div class="formBackground">
<div style="position:absolute;top:50px;left:100px;width:450px;height:25px;background-color:#fff;color:#000;">When this reaches the top, the "trigger" should say 50px"</div>
</div>
</div>
</div>
<div class="formContainer" style="width:816px;height:1056px" id="xzOwqphM4GGR_2">
<div class="formContent">
<div class="formBackground"><div style="position:absolute;top:50px;left:100px;width:450px;height:25px;background-color:#fff;color:#000;">This should still say 50px</div></div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
CSS
#pageContent {
position:relative;
padding-bottom:20px;
background-color:#fff;
z-index:2;
overflow:auto;
-webkit-overflow-scrolling: touch;
-webkit-transform: translate(0, 0);
-moz-transform: translate(0, 0);
-ms-transform: translate(0, 0);
transform: translate(0, 0);
}
#formBox {
display: block;
position: relative;
height: 100%;
padding: 15px;
}
.trimSpace {
display: block;
position: relative;
margin: 0 auto;
padding: 0;
height: 100%;
overflow: hidden;
}
#formScale::after {
display: block;
content:'';
padding-bottom:5px;
}
#formScale {
position:relative;
width:816px;
margin:0;
-webkit-transform-origin: 0 0;
-moz-transform-origin: 0 0;
transform-origin: 0 0;
-ms-transform-origin: 0 0;
}
.formContainer {
position:relative;
margin : 0 auto 15px auto;
padding:0;
}
.formContent {
position:absolute;
width:100%;
height:100%;
}
.formBackground {
width:100%;
height:100%;
background-color:red;
}
JS
var PAGEWIDTH = 816;
$(window).resize(function (e) {
zoomProject();
resize();
});
function resize() {
$("#pageContent").css('height', window.innerHeight - 45 + 'px');
}
function zoomProject() {
var maxWidth = $("#formBox").width(),
percent = maxWidth / PAGEWIDTH;
$("#formScale").css({
'transform': 'scale(' + percent + ')',
'-moz-transform': 'scale(' + percent + ')',
'-webkit-transform': 'scale(' + percent + ')',
'-ms-transform': 'scale(' + percent + ')'
});
$(".trimSpace").css('width', (PAGEWIDTH * percent) + 'px');
$("#formBox, .trimSpace").css('height', ($("#formScale").height() * percent) + 'px');
}
zoomProject();
resize();
EDIT:
I don't think I am conveying a good job at relaying what I want to accomplish.
At the moment there are two .formContainer's. When I scroll #pageContainer, the .formContainer divs move up through #pageContainer.
So what I want to accomplish is, when a user clicks the "ME" button or #click (as shown in the fiddle below), I'd like to know where in that particular .formContainer, is it touching the top of #pageContainer.
I do use scroll spy in my real world application so I know which .formContainer is closest to the top. So if you just want to target one .formContainer, that is fine.
I used these white div elements as an example. If I am scrolling #pageContainer, and that white div element is at the top of screen as I am scrolling and I click on "ME", the on click trigger should alert to me that .formContainer is touching the top of #pageContainer at 50px from the top. If, the the red container is just touching the top of #pageContainer, it should say it is 0px from the top.
I hope that helps clear up some misconception.
Here is an updated jsfiddle that shows the kind of action that I want to happen.
I am giving this a stab because I find these things interesting. It might just be a starting point since I have a headache today and am not thinking straight. I'd be willing to bet it can be cleaned up and simplified some.
I also might be over-complicating the approach I took, getting the first visible form, and the positioning. I didn't use the getBoundingClientRect function either.
Instead, I approached it trying to account for padding and margin, using a loop through parent objects up to the pageContent to get the offset relative to that element. Because the form is nested a couple levels deep inside the pageContent element you can't use position(). You also can't use offset() since that changes with scroll. The loop approach allowed me to factor the top margin/padding in. I haven't looked at the other solutions proposed fully so there might be a shorter way to accomplish this.
Keeping in mind that the scale will affect the ACTUAL location of the child elements, you have to divide by your scale percentage when getting the actual location. To do that I moved the scalePercentage to a global var so it was usable by the zoom function and the click.
Here's the core of what I did. The actual fiddle has more logging and junk:
var visForm = getVisibleForm();
var formTop = visForm.position().top;
var parents = visForm.parentsUntil('#pageContent');
var truOffset = 0;
parents.each(function() {
truOffset -= $(this).position().top;
});
// actual location of form relative to pageContent visible pane
var formLoc = truOffset - formTop;
var scaledLoc = formLoc / scalePercent;
Updated Fiddle (forgot to account for scale in get func): http://jsfiddle.net/e6vpq9c8/5/
If I understand your question correctly, what you want is to catch when certain descendant elements reach the top of the outer container, and then determine the position of the visible "page" (div with class formContainer) relative to the top.
If so, the first task is to mark the specific elements that could trigger this:
<div class='triggerElement' style="position:absolute;top:50px;left:100px;width:450px;height:25px;background-color:#fff;color:#000;">When this reaches the top, the "trigger" should say 50px"</div>
Then the code:
// arbitrary horizontal offset - customize for where your trigger elements are placed horizontally
var X_OFFSET = 100;
// determine once, at page load, where outer container is on the page
var outerContainerRect;
$(document).ready(function() {
outerContainerRect = $("#pageContent").get(0).getBoundingClientRect();
});
// when outer container is scrolled
$("#pageContent").scroll(function(e) {
// determine which element is at the top
var topElement = $(document.elementFromPoint(outerContainerRect.left+X_OFFSET, outerContainerRect.top));
/*
// if a trigger element
if (topElement.hasClass("triggerElement")) {
// get trigger element's position relative to page
console.log(topElement.position().top);
}
*/
var page = topElement.closest(".formContainer");
if (page.length > 0) {
console.log(-page.get(0).getBoundingClientRect().top);
}
});
EDIT: Changed code to check formContainer elements rather than descendant elements, as per your comment.
http://jsfiddle.net/j6ybgf58/23/
EDIT #2: A simpler approach, given that you know which formContainer to target:
$("#pageContent").scroll(function(e) {
console.log($(this).scrollTop() - $("#xzOwqphM4GGR_1").position().top);
});
http://jsfiddle.net/rL4Ly3yy/5/
However, it still gives different results based on the size of the window. This seems unavoidable - the zoomProject and resize functions are explicitly resizing the content, so you would have to apply the inverse transforms to the number you get from this code if you want it in the original coordinate system.
I do not fully understand what it is that you are needing, but if i am correct this should do the trick
$("#pageContent").scroll(function(e) {
// If more then 50 pixels from the top has been scrolled
// * if you want it to only happen at 50px, just execute this once by removing the scroll listener on pageContent
if((this.scrollHeight - this.scrollTop) < (this.scrollHeight - 50)) {
alert('it is');
}
});
ScrollHeight is the full height of the object including scrollable pixels.
ScrollTop is the amount of pixels scrolled from the top.
You can use waypoints to detect the position of divs based on where you're scrolling.
Here is a link to their official website's example: http://imakewebthings.com/waypoints/shortcuts/inview/

How to color an image based on a dynamically changing percentage value?

I have an image which i want to fill with some color based on a dynamically changing value that represents percentage, e.g. if the value is 50% half of the image should be colored.
How to achieve that using JavaScript (jQuery can be used)?
You can accomplish that by utilizing the clip CSS property, and a little bit of added markup to serve an underlaying container for the unrevealing background color.
Abstract
Place an element underneath the image for the faux color fill, and set its dimensions and position to match the images'. Then use JavaScript to clip the image dynamically - thus revealing the underlying color - by altering the clip value of the image element according to your needs (you can, of course, control each offset separately, e.g. left, bottom).
Note: To achieve specifically what you desire you can alter the underlying element to contain another image that suits your needs (i.e. a top image of an empty barrel, and a bottom image of a full one).
Implementation
In this example a slider is used to trigger the value change.
Markup
<input type="range" min="0" max="100" id="slider" value="100" />
<div id="underlay"></div>
<img src="http://placekitten.com/500/207" id="image" />
Styles
#slider,
#image,
#underlay {
/* absolute positioning is mandatory for clipped elements (#image) */
position: absolute;
left: 50px;
width: 500px;
}
#image,
#underlay {
top: 100px;
height: 207px;
}
#image {
/* initial clip state */
clip: rect(auto, auto, auto, 500px);
}
#slider {
top: 50px;
}
#underlay {
background-color: #4C76A5;
}
Functionality
var img = document.getElementById('image');
var sld = document.getElementById('slider');
sld.addEventListener('change', function(e) {
// get the slider value
var val = e.srcElement.value;
// calc the percentage to pass an absolute length value to the clip property
var perc = img.width / 100 * val;
// set the images' left offset clip accordingly
img.style.clip = 'rect(auto, auto, auto, ' + perc + 'px)';
});
Live Demo
On jsFiddle
References
clip on Mozilla Developer Network
Browser support

object positioning with style.position

I want to be able to move an object from one position to another using buttons for example 3 buttons left right and center that always put the object at the exact same positions everytime.
I have tried using style.position="absolute" but it moves left or right or whatever position depending on it last position never the same three positions.
Which one of static, absolute, fixed, relative, inherit would be best to use in this case? and is it possible to get an example of how a particular object would be set to a particular position thanks in advance
position: absolute tells the browser HOW to position your element, but not WHERE to position your element. You still need to position your element by setting left or right and top or bottom values in your css.
Given some markup:
<button id="left">LEFT</button>
<button id="right">RIGHT</button>
<button id="center">CENTER</button>
<div id="wrapper">
<div id="thingy"/>
</div>​
and some styles:
#wrapper {
position: relative;
margin-top: 10px;
}
#thingy {
margin: 0 auto;
width: 25px;
height: 25px;
background-color: #f69;
}​
You can move the thingy this way:
var thingy = document.getElementById('thingy');
document.getElementById('left').onclick = function() {
thingy.style.position = 'absolute';
thingy.style.right = null;
thingy.style.left = 0;
};
document.getElementById('right').onclick = function() {
thingy.style.position = 'absolute';
thingy.style.left = null;
thingy.style.right = 0;
};
document.getElementById('center').onclick = function() {
thingy.style.position = 'inherit';
thingy.style.left = null;
thingy.style.right = null;
};
​
Code is posted at: http://jsfiddle.net/TXWfh/1/
You could make it work with absolute, but I think relative might work best in your case.
HTML
<div id="test"></div>
<input type="button" id="left" value="Left">
<input type="button" id="middle" value="Middle">
<input type="button" id="right" value="Right">
CSS
#test {
position: relative;
width: 100px;
height: 100px;
background-color: red;
}
input {
padding: 4px;
}
JavaScript
var test = document.getElementById("test");
document.getElementById("left").onclick = function() {
test.style.left = "0px";
};
document.getElementById("middle").onclick = function() {
test.style.left = "250px";
};
document.getElementById("right").onclick = function() {
test.style.left = "500px";
};
Live example
Don't know what you are trying to do, but it really doesn't matter if you don't have a lot of surrounding html code that can get affected by your choice of position value.
following 3 fiddles do same things but with subtle difference that they have different value for position attribute.
http://jsfiddle.net/9uQT8/3/
http://jsfiddle.net/9uQT8/4/
http://jsfiddle.net/9uQT8/5/
try clicking left center right. and see. I used jQuery though, super cool JS framework.

Positioning a "reel" div properly in jQuery when total width is dynamic

I'm writing my own small pager control in Javascript and jQuery and having trouble positioning it properly.
The pager is set to only be a specific width (340px in this case) which allows it to display roughly ten page buttons. If the user has selected a higher page, I'd like the reel to slide to the left and show the selected page in the center. Since the number of pages is set dynamically (I build the pager in js when the page is loaded) and their width is not constant (double-digit page number buttons are wider than single-digit buttons) how can I determine and then set the pager to the correct position?
I was attempting to use the following code:
(where my buttons are labeled "#Nav1", "#Nav2", etc...)
if (currentPage < 7) {
newPos = 0;
}
else {
newPos = $('#Nav' + (currentPage-5)).position().left;
}
$('#reel').animate({left: newPos*-1}, 700);
But the #reel div is wrapping so position().left doesn't return the position I need.
Suggestions?
Here is my HTML/CSS markup:
<style type="text/css">
div#pager div
{
display: inline-block;
}
#navContainer
{
width: 340px;
height: 28px;
overflow: hidden;
position: relative;
}
#reel
{
position: absolute;
top: 0;
left: 0;
}
</style>
<div id="pager" class="buttons">
<div id="preButtons"></div>
<div id="navContainer">
<div id="reel">
</div>
</div>
<div id="postButtons"></div>
</div>
You'll need to manually give #reel a width equivalent to the number of items * the width of each item.
A dynamic way to do this is to load in all of the items, place them in a hidden, unbounded div, then set the width of #reel equal to the width of that div.
Try this before your carousel code:
var dummyDiv = $('<div id="dummy" class="buttons" style="position:absolute;display:none"></div>');
dummyDiv.appendTo('body');
dummyDiv.html($('#reel').html());
var reelWidth = dummyDiv.css('width');
$('#reel').css({'width':reelWidth});
This will allow you to dynamically set the width of the #reel div so it doesn't wrap without knowing the exact size of the contents statically.

Categories

Resources