'Keep constraints between two elements when one is getting off screen

I would like an element "1" to go off screen (off red square in my example) and to be followed by an element "2" without having to use anything else than CSS constraints.

const right = document.getElementById('right');
const left = document.getElementById('left');

function r(min, max) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

right.style.width = `${r(10, 30)}%`;
left.style.width = `${r(10, 30)}%`;

left.onclick = () => {
    left.classList.toggle('retracted');
}
* {
    padding: 0;
    margin: 0;
    font-family: 'Courier New', Courier, monospace;
    font-weight: bold;
}

body {
    width: 100vw;
    height: 100vh;
}

.container {
    width: 50vw;
    height: 50vh;
    transform: translate(-50%, -50%);
    left: 50vw;
    top: 50vh;
    border: 3px solid red;
    position: absolute;
    display: flex;
    flex-direction: row;
    box-sizing: border-box;
}

.panel {
    width: 30%;
    border: 1px solid rgba(0, 0, 0, 0.1);
    height: 100%;
    position: relative;
    display: flex;
    justify-content: center;
    align-items: center;
    resize: horizontal;
    user-select: none;
    box-sizing: border-box;
    transition: all .2s;
}

.left {
    background-color: rgb(95, 158, 160, 0.5);
}

.left.retracted {
    transform: translateX(-100%);
}

.right {
    background-color: rgb(255, 228, 196, 0.5);
    right: 0;
}

.filler {
    flex: 1;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="index.css" />
    <title>Slide</title>
</head>
<body>
    <div class="container">
        <div id="left" class="panel left">
            1
        </div>

        <div id="right" class="panel right">
            2
        </div>

        <div class="filler"></div>
    </div>

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

I can do this by hand by computing sizes and everything I want in js but it's not my goal, I was wondering if there was any way to keep constraints between these two elements and keep the animation.

I only do:

right.style.width = `${r(10, 30)}%`;
left.style.width = `${r(10, 30)}%`;

this to simulate the fact that the two elements does not have the same size and that I'm looking for a self-compute-less solution.



Solution 1:[1]

Check this


Using variables will solve width problem.

Basically you are using variables to make right container move as left container.


:root{
  --left-width: 30%;
  --right-width: 50%;
  --left-value:30;
  --right-value:50;
}

.right.retracted{
  transform: translateX(calc(-100% * var(--left-value) / var(--right-value)));
}

const leftContainer = document.querySelector('.left');
const rightContainer = document.querySelector('.right');

leftContainer.onclick = () => {
    rightContainer.classList.toggle('retracted');
    leftContainer.classList.toggle('retracted');
    
}
*,
*::before,
*::after {
  box-sizing: border-box;
}



body{
  min-height: 100vh;
  overflow: hidden;
  display: grid;
  place-content: center;
  margin:0;
  background-color: white;
}

.container {
  width: 50vw;
  height: 50vh;
  margin:auto;
  border: 3px solid red;
  display: flex;
  flex-direction: row;
  justify-content: start;
  transition: all 1s;
}

.panel {
  border: 1px solid rgba(0, 0, 0, 0.1);
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  resize: horizontal;
  user-select: none;
  transition: all .2s;
}

:root{
  --left-width: 30%;
  --right-width: 50%;
  --left-value:30;
  --right-value:50;
}

.left {
  width: var(--left-width);
  background-color: rgb(95, 158, 160, 0.5);
}
.left.retracted{
  transform: translateX(-100%);
  transition: all .2s; 
}

.right {
  width:var(--right-width);
  background-color: rgb(255, 228, 196, 0.5);
}

.filler {
  flex: 1;
}

.right.retracted{
  transform: translateX(calc(-100% * var(--left-value) / var(--right-value)));
  transition: all .2s; 
}
<div class="container">
      <div class="panel left">
        <p>1</p>
      </div>

      <div class="panel right">
        <p>2</p>
      </div>

      <div class="filler"></div>
</div>

Solution 2:[2]

i put width of .right and .left into a css variable. and i useed variables to change the position of elements.

const container = document.getElementById('container');
const left = document.getElementById('left');


left.onclick = () => {
    container.classList.toggle('retracted');
}
* {
    padding: 0;
    margin: 0;
    font-family: 'Courier New', Courier, monospace;
    font-weight: bold;
}

body {
    width: 100vw;
    height: 100vh;
}

.container {
/* maximum width 100% */
  --leftWidth: 15%;
  --rightWidth: 35%;
    width: 50vw;
    height: 50vh;
    transform: translate(-50%, -50%);
    left: 50vw;
    top: 50vh;
    border: 3px solid red;
    position: absolute;
    display: flex;
    flex-direction: row;
    box-sizing: border-box;
}

.panel {
    border: 1px solid rgba(0, 0, 0, 0.1);
    height: 100%;
    position: relative;
    display: flex;
    justify-content: center;
    align-items: center;
    resize: horizontal;
    user-select: none;
    box-sizing: border-box;
    transition: all .2s;
}

.left {
    background-color: rgb(95, 158, 160, 0.5);
    width: var(--leftWidth);
    left:0;
}

.retracted .left {
    left: calc(var(--leftWidth) * -1);
}

.right {
    background-color: rgb(255, 228, 196, 0.5);
    width: var(--rightWidth);
    left:0;
}
.retracted .right{
    left: calc(var(--leftWidth) * -1);

}

.filler {
    flex: 1;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="index.css" />
    <title>Slide</title>
</head>
<body>
    <div class="container" id="container">
        <div id="left" class="panel left">
            1
        </div>

        <div id="right" class="panel right">
            2
        </div>

        <div class="filler"></div>
    </div>

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2