0

I know how to move a div element using top/left position increments. But I heard that translate3d gives performance improvements, and so I wanted to check it out.

Lets say I have this,

var div = document.createElement('div');
div.style.width = '100px';
div.style.height = '100px';
div.style.background = 'red';
div.style.position = 'absolute';
document.body.appendChild(div);

The following,

div.getBoundingClientRect().left

gives the value 8.

Now, calling

div.style.transform = 'translate3d(10px,0,0)'

moves the element to the right by 10px as expected as seen by

div.getBoundingClientRect().left

giving the value 18.

But repeatedly executing,

div.style.transform = 'translate3d(10px, 0, 0)';

does not move the div to the right repeatedly. Instead I have to increment the first argument of translate3d to higher values (eg. 20, 30 etc) to move it repeatedly.

This makes me think the translation is calculated from some initial point. But googling around, I could not find how to update this initial point, so that translate3d(10px, 0, 0) works in a loop.

I tried updating the div.style.left property, but it still does not work.

So can someone tell me how translate3d calculates the translation? And if there is a way to use translate3d(10px,0,0) and some origin resetting, to make a div move repeatedly?

(The end goal is to make a div move to the right every time it is clicked, using translate3d)

5 Answers5

0
translate3d(10px,0,0)

Is defined here: https://www.w3.org/TR/css-transforms-1/#funcdef-translate3d

I wrote a CodePen to see your issue: https://codepen.io/cyruscuenca/pen/mamjgw?&editable=true

I also scanned the doc, and it seems like the position is based on a coordinate system, and there seems to be separate origin properties. I would read those sections.

The sections that I found that look to be related are 6 and 3.

Hope this helps!

Cyrus
  • 613
  • 1
  • 6
  • 22
0

It's very simple. For the point of the count this function is used your current coordinates by X, Y and Z axes. So, if you want to move your block, you should always increment this valuse(these values) of the axes.

Valerii Voronkov
  • 339
  • 1
  • 4
  • 16
0

When you are doing div.style.transform = 'translate3d(10px, 0, 0)'; you are just removing the transform value to replace it by another, so your element will keep the same position. If you want to move your element using translate3d, you can use a variable and increment it every click :

var div = document.createElement('div');
div.style.width = '100px';
div.style.height = '100px';
div.style.background = 'red';
div.style.position = 'absolute';
document.body.appendChild(div);

var leftValue = 0;
var increment = 10;

var button = document.querySelector('button');

button.addEventListener('click', function(event) {
  leftValue += increment;
  div.style.transform = 'translate3d('+leftValue+'px,0,0)';
})
<button>Move</button>

For multiple elements, using getBoundingClientRect().left

When calculating a new offset, we have to take into account the initial position of the element or the new value will be wrong. The problem can be see here.

To avoid this, we can store the initial position of the element and then substract this value when calculating the new offset:

var increment = 10;
var moveBoxes = document.getElementsByClassName('moveBox');

Array.from(moveBoxes).forEach(function(box) {
  var initialOffset = getComputedStyle(box).getPropertyValue('left').replace('px', '');
  
  box.addEventListener('click', function(event) {
    var offset = this.getBoundingClientRect().left - initialOffset + increment;
    this.style.transform = 'translate3d('+offset+'px,0,0)';
  });
});
.moveBox {
  width: 100px;
  height: 100px;
  background: red;
  position: absolute;
}
<div class="moveBox" style="left: 32px;"></div>
<div class="moveBox" style="top: 120px; left: 100px;"></div>

Using a forEach loop instead of a classical for loop let us store the right initialOffset value in each listeners (there are some others ways to achieve this). Since the array of div is a HTML collection, using Array.from is required to use forEach.

Arkellys
  • 5,562
  • 2
  • 16
  • 40
  • I wanted to come up with a scale-able solution. If I have multiple divs with a common class name, then I want this to work for all of them. Using an offset value would require me to maintain one for each of those divs. And even if I do that, once I get `event.target` from the click callback, how do I map the div to the correct offset? I would have to scan an entire array of offsets for that right? I am trying to avoid that by making use of only values returned by `elem.getBoundingClientRect()`. – all_the_questions Dec 24 '18 at 08:53
  • Of course!! That is the kind of concise solution that I was looking for. Thanks :) – all_the_questions Dec 24 '18 at 09:39
  • I have tried out the above solution and it works well. But I found a glitch, in that the initial position influences the increment. For example, if my div has an initial left position of 8px, then on clicking the increment will be 18px. That is, instead of moving to 18px, it moves to 8 + 18 = 26 px. Subsequent increments also are by 18px. This will become a problem if the initial left value is a big number, right? Is there a way to embed the initial position of the div as a literal within its callback, when defining the callback in the loop? – all_the_questions Dec 24 '18 at 10:44
  • Ahh nice catch, I didn't think about that. I quick fix would be to set the intial `left` value in the CSS, then catch this value in the function and substract it to the offset value. But maybe there is another way ? I have to think about it... I edited the question to show what I mean. – Arkellys Dec 24 '18 at 11:12
  • I have added a solution which I think holds. But I am a JS newbie, so it would be great if you could see if my solution would hold. It works for me so far, but I have not tested it much. – all_the_questions Dec 24 '18 at 11:44
  • @all_the_questions I edited my answer again with a solution. There are probably some others ways to do it, as the one you used for example, but I can't tell what would be the best. – Arkellys Dec 24 '18 at 14:35
0

You can apply multiple transformations on the same element.

div.style.transform = 'translate3d(10px,0,0) translate3d(10px,0,0) translate3d(10px,0,0)';

Which would mean you need to append the string on every loop iteration:

div.style.transform += ' translate3d(10px,0,0)';

However, if you have too many transformations, the string would become very long and might impact the performance, let alone the readability.

If it's always the same transformation you're applying, why not increase the 10px along the way while iterating through the loop:

var offset = 10;

// then on every iteration
div.style.transform = 'translate3d(' + offset + 'px,0,0)';
offset += 10;
Imantas
  • 1,621
  • 13
  • 19
0

Taking ideas from @Arkellys answer (the accepted one), I came up with a solution which seems to be working fine for me. I am a JS newbie so I do not know how well it will hold up. Anyways here it is in case it helps anyone,

// Create container
var container = document.createElement('div');
container.style.width = '1000px';
container.style.height = '500px';
container.style.border = '2px solid black';
container.style.background = 'lightblue';
container.style.margin = '0';
container.style.padding = '10px';

document.body.appendChild(container);
var containerPosition = container.getBoundingClientRect();


// 'click' Callback function generator. 
// I pass the initial position to the generator, who then generates a callback
// function which holds this value for use when the event occurs.
function clickCallbackGenerator(initialPos, increment){
    var left = initialPos.left;
    var clickCallback = function(event) {
        var newLeftPosition = this.getBoundingClientRect().left - left + increment;
        this.style.transform = `translate3d(${newLeftPosition}px, 0, 0)`;
        console.log('clicked : ', this.innerHTML,' now at : ', this.getBoundingClientRect().left);
    };

    return clickCallback;
}

// function to create 'move-on-click' div nodes
function createNodeDiv(position, text) {
    var div = document.createElement('div');
    div.style.position = 'absolute';
    div.style.top = `${position.top}px`;
    div.style.left = `${position.left}px`;
    div.style.border = '2px solid blue';
    div.style.background = 'black';
    div.style.color = 'white';
    div.style.margin = '0';
    div.style.padding = '0';
    div.style.cursor = 'pointer';
    div.innerHTML = text;

    div.setAttribute('class', 'node');
    div.setAttribute('contenteditable', 'true');
    container.appendChild(div);

    div.addEventListener('click', clickCallbackGenerator(div.getBoundingClientRect(), 10));

    console.log('Created : ', div.innerHTML, ' at : ', div.getBoundingClientRect().left);
    return div;
}

var defaultPosition = {x: containerPosition.left, y: containerPosition.top};
var increment = 10;



var nodes = [];
nodes.push(createNodeDiv({left: containerPosition.left, top: containerPosition.top}, 'hello'));
nodes.push(createNodeDiv({left: containerPosition.left+50, top: containerPosition.top+50}, 'world'));
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="description" content="Free Web tutorials">
        <meta name="keywords" content="HTML,CSS,XML,JavaScript">
        <meta name="author" content="John Doe">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" type="text/css" href="style.css">
    </head>

    <body>
        <script src="script.js"></script>
    </body>
</html>

I used a function clickCallbackGenerator to generate the callbacks for each moveable div. That is the only thing I changed.