admin管理员组

文章数量:1291007

I'm trying to make a chat app and at the press of a button you can see timestamps of messages slide in.

My timestamp always takes 200px in size and the other takes the rest.

Let's call div1 for the timestamp (200px) and div2 for the messages (takes up the rest of the space).

I tried using the translate on the first div1 and to move and quickly realised that the div2 won't resize automatically:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        *{
            padding: 0px;
            margin: 0px;
            box-sizing: border-box;
        }
        .chat-container {
            display: flex;
            height: 50vh;
            width: 50vw;
            background-color: #f0f0f0;
        }
        #div1 {
            background-color: #0004ff;
            height: 100%;
            width: 200px;
            text-align: center;
            
            position: relative;
            left:-200px;
            transition: left 1s;
        }
        #div2 {
            background-color: #ff0000;
            height: 100%;
            text-align: center;
            width: calc(100% - 200px); /* 100% - 200px or auto would work*/
        }
    </style>
</head>
<body>
    <div class="chat-container">
        <div id="div1">
            <p>div1</p>
        </div>
        <div id="div2">
            <p>div2</p>
        </div>
    </div>
    <button id="button">animation</button>
    <script>
        let actived = false;
        document.getElementById("button").addEventListener("click", function() {
            const div1 = document.getElementById("div1");
            const div2 = document.getElementById("div2");
            div1.style.left = actived ? "-200px" : "0px";
            actived = !actived;
        });
    </script>
</body>
</html>

So I tried resizing the size of the div2 but when then I did calc(100% - 200px) it wasn't the same size!!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        *{
            padding: 0px;
            margin: 0px;
            box-sizing: border-box;
        }
        .chat-container {
            display: flex;
            height: 50vh;
            width: 50vw;
            background-color: #f0f0f0;
            overflow: hidden;
        }
        #div1 {
            background-color: #0004ff;
            height: 100%;
            width: 200px;
            text-align: center;
            
            position: relative;
            left:-200px;
            transition: left 1s;
        }
        #div2 {
            background-color: #ff0000;
            height: 100%;
            text-align: center;
            width: calc(100% - 200px); /* 100% - 200px or auto would work*/

            position: relative;
            transition: width 1s;
        }
    </style>
</head>
<body>
    <div class="chat-container">
        <div id="div1">
            <p>div1</p>
        </div>
        <div id="div2">
            <p>div2</p>
        </div>
    </div>
    <button id="button">animation</button>
    <script>
        let actived = false;
        document.getElementById("button").addEventListener("click", function() {
            const div1 = document.getElementById("div1");
            const div2 = document.getElementById("div2");
            div1.style.left = actived ? "-200px" : "0px";
            div2.style.width = actived ? "100%" : "calc(100% - 200px)";
            actived = !actived;
        });
    </script>
</body>
</html>

How can 100% of the parent minus the div1's size isn't the rest? I'm so confused.

Please tell me what method I should use and why 100% - 200px doesn't line up.

I'm trying to make a chat app and at the press of a button you can see timestamps of messages slide in.

My timestamp always takes 200px in size and the other takes the rest.

Let's call div1 for the timestamp (200px) and div2 for the messages (takes up the rest of the space).

I tried using the translate on the first div1 and to move and quickly realised that the div2 won't resize automatically:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        *{
            padding: 0px;
            margin: 0px;
            box-sizing: border-box;
        }
        .chat-container {
            display: flex;
            height: 50vh;
            width: 50vw;
            background-color: #f0f0f0;
        }
        #div1 {
            background-color: #0004ff;
            height: 100%;
            width: 200px;
            text-align: center;
            
            position: relative;
            left:-200px;
            transition: left 1s;
        }
        #div2 {
            background-color: #ff0000;
            height: 100%;
            text-align: center;
            width: calc(100% - 200px); /* 100% - 200px or auto would work*/
        }
    </style>
</head>
<body>
    <div class="chat-container">
        <div id="div1">
            <p>div1</p>
        </div>
        <div id="div2">
            <p>div2</p>
        </div>
    </div>
    <button id="button">animation</button>
    <script>
        let actived = false;
        document.getElementById("button").addEventListener("click", function() {
            const div1 = document.getElementById("div1");
            const div2 = document.getElementById("div2");
            div1.style.left = actived ? "-200px" : "0px";
            actived = !actived;
        });
    </script>
</body>
</html>

So I tried resizing the size of the div2 but when then I did calc(100% - 200px) it wasn't the same size!!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        *{
            padding: 0px;
            margin: 0px;
            box-sizing: border-box;
        }
        .chat-container {
            display: flex;
            height: 50vh;
            width: 50vw;
            background-color: #f0f0f0;
            overflow: hidden;
        }
        #div1 {
            background-color: #0004ff;
            height: 100%;
            width: 200px;
            text-align: center;
            
            position: relative;
            left:-200px;
            transition: left 1s;
        }
        #div2 {
            background-color: #ff0000;
            height: 100%;
            text-align: center;
            width: calc(100% - 200px); /* 100% - 200px or auto would work*/

            position: relative;
            transition: width 1s;
        }
    </style>
</head>
<body>
    <div class="chat-container">
        <div id="div1">
            <p>div1</p>
        </div>
        <div id="div2">
            <p>div2</p>
        </div>
    </div>
    <button id="button">animation</button>
    <script>
        let actived = false;
        document.getElementById("button").addEventListener("click", function() {
            const div1 = document.getElementById("div1");
            const div2 = document.getElementById("div2");
            div1.style.left = actived ? "-200px" : "0px";
            div2.style.width = actived ? "100%" : "calc(100% - 200px)";
            actived = !actived;
        });
    </script>
</body>
</html>

How can 100% of the parent minus the div1's size isn't the rest? I'm so confused.

Please tell me what method I should use and why 100% - 200px doesn't line up.

Share Improve this question edited Feb 13 at 19:07 Wongjn 24.5k3 gold badges20 silver badges43 bronze badges asked Feb 13 at 16:43 XCZ_MikeXCZ_Mike 231 silver badge3 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 2

Explanation

The difference when toggling is due to implicit flex-shrink behavior (since elements have a default of flex-shrink: 1 in flex layouts:

The flex-shrink property manages removing negative free space to make boxes fit into their container without overflowing. Removing space is a bit more complicated than adding space. The flex shrink factor is multiplied by the flex base size; this distributes negative space in proportion to how much the item can shrink. This prevents smaller items from shrinking to 0px before a larger item is noticeably reduced.

This may be better demonstrated through example.

Say chat-container is 500px wide.

The intrinsic widths of the elements before any flex shrinking has taken place is:

div1 div2
200px 100% - 200px
= 500px - 200px
= 300px

No shrinking happens, everything fits exactly.

Click the animation button twice and then div2's width is now 100%, thus:

div1 div2
200px 100%
= 500px

Now, div1 and div2 are now wider than the container, since changing left on div1 does not remove the space it takes up. Thus, flex shrinking calculations happen as follows:

Calculate the surplus widths of our child elements:

200px + 500px - 500px = 200px;
 ↑        ↑       ↑
div1     div2  container

The ratio of div1:div2 is 2:5, thus we allocate this 200px surplus between them as follows:

div1 div2
200px ÷ 7 × 2
≈ 57px
200px ÷ 7 × 5
≈ 143px

Thus the final widths are as follows:

div1 div2
143px 357px

Solution

If you want to actually slide the div1 in and out, you can use margin, and let div2 fill up the space naturally with flex-grow: 1:

let actived = false;
document.getElementById("button").addEventListener("click", function() {
  const div1 = document.getElementById("div1");
  div1.style.marginLeft = actived ? "-200px" : "0px";
  actived = !actived;
});
* {
  padding: 0px;
  margin: 0px;
  box-sizing: border-box;
}

.chat-container {
  display: flex;
  height: 50vh;
  width: 50vw;
  background-color: #f0f0f0;
  overflow: hidden;
}

#div1 {
  background-color: #0004ff;
  height: 100%;
  width: 200px;
  text-align: center;

  position: relative;
  margin-left: -200px;
  transition: margin-left 1s;
}

#div2 {
  background-color: #ff0000;
  height: 100%;
  text-align: center;
  flex-grow: 1;
}
<div class="chat-container">
  <div id="div1">
    <p>div1</p>
  </div>
  <div id="div2">
    <p>div2</p>
  </div>
</div>
<button id="button">animation</button>

There are a lot of parts that go into a chat app so I made a simple one and then animated the rows as they are made. A simple animation can break easily if the layout is complex, elements are added dynamically, or if there's scrolling involved etc. Also, the first column is 40% width instead of 200px because absolute widths are not responsive. Details are commented in example. View in Full page mode.

BTW, mathematically calc(100% - 200px) should work but if the parent has left and/or right padding or if there's a gap property or if there's a scrollbar etc. you need to include that in the formula as well.

// Reference <form>
const chat = document.forms.chat;
/*
Reference all:
  - <fieldset>
  - <textarea>
  - <button>
*/
const io = chat.elements;
// Reference fieldset#scroller
const scr = io.scroller;
// Reference fieldset#box
const box = io.box;
// Reference <textarea>
const msg = io.message;
// Register <form> to listen for the "submit" event.
chat.addEventListener("submit", send);

/**
 * Creates a current timestamp in the following format:
 * YY-MM-DD HH:MM:SS
 * @return {string} - YY-MM-DD HH:MM:SS
 */
function tStamp() {
  const now = new Date()
  let dT = Date.parse(now.toISOString().slice(0, -5));
  return new Date(dT - (now.getTimezoneOffset() * 120000)).toISOString().slice(2, -5).split("T").join(" ");
}

/**
 * Adds a given string (@param br) at the end of every
 * given number of characters (@param max) in a given 
 * string (@param string).
 * @param {string} string - String to add line breaks to
 * @param {number} max    - Number of characters per line.
 * @param {string} br     - String to add to the end of 
 * @default "\n"            each line.
 * @return {string}       - String with line breaks
 * https://www.30secondsofcode./js/s/word-wrap/
 */     
function lBreak(string, max, br = '\n') {
  const rgx = new RegExp(`(?![^\\n]{1,${max}}$)([^\\n]{1,${max}})\\s`, 'g');
  return string.replace(rgx, `$1${br}`);
}

/**
 * "submit" event handler creates a two column row consisting of
 * a timestamp and the text message the user submitted.
 * The .expand class is added to each column to animate them.
 * Scrolling is kept at the bottom of fieldset#scroller.
 * textarea#message is cleared.
 * @param {object} e - Event object
 */
function send(e) {
  e.preventDefault();
  const time = document.createElement("fieldset");
  time.className = "time";
  time.innerHTML = `
      <time>${tStamp()}</time>`;
  const text = document.createElement("fieldset");
  text.className = "text";
  text.innerHTML = `
      <p><b>User </b>${lBreak(msg.value, 32)}</p>`;
  box.append(time, text);
  setTimeout(() => {
    time.classList.add("expand");
  }, 500);
  setTimeout(() => {
    text.classList.add("expand");
  }, 750);
  setTimeout(() => {
    box.lastElementChild.scrollIntoView({
      behavior: "smooth"
    });
  }, 1000);
  msg.value = "";
}
:root {
  font: 2ch/1.3 "Segoe UI";
  overflow-x: hidden;
}

form {
  width: 50vw;
  margin: 0 auto;
  padding: 8px;
  border-radius: 8px;
  background: #677680;
}

fieldset {
  padding: 0;
  border: 0;
}

p {
  margin: 0;
  padding: 0 5px;
}

.frame {
  padding: 5px 5px 0;
  border-radius: 8px;
  background: #aec7d6;
  overflow: hidden;
}

#scroller {
  display: flex;
  flex-direction: column;
  height: 50vh;
  overflow-y: scroll;
  overflow-x: hidden;
}

#box {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  align-content: flex-start;
  gap: 3px;
  height: min-content;
  background: #677680;
}
/*
width, max-width, font-size, and opacity are animated for
all fieldset.time and fieldset.text when the .expand class
is assigned to them. A ✱ denotes the relevant rulesets.
*/
.time {
  width: 0; /* ✱ */
  max-width: 0; /* ✱ */
  margin-left: 3px;
  font-size: 0; /* ✱ */
  font-family: Consolas;
  color: #fff;
  background: transparent;
  transition: all 1.4s; /* ✱ */
  opacity: 0; /* ✱ */
  overflow: hidden; /* ✱ */
}

.text {
  width: 0; /* ✱ */
  height: auto; 
  max-width: 0; /* ✱ */
  border-radius: 4px;
  background: #dae9f2;
  transition: max-width 1.2s; /* ✱ */
  overflow-y: auto;
}

.expand {
  font-size: 1rem; /* ✱ */
  opacity: 1; /* ✱ */
}

.expand.time {
  width: 40%; /* ✱ */
  max-width: 40%; /* ✱ */
  height: 1.3rem;
}

.expand.text {
  width: 55%; /* ✱ */
  max-width: 55%; /* ✱ */
}

.control {
  display: grid;
  grid-template-columns: 2fr 1fr;
  margin-top: 5px;
}

#message {
  border: 0;
  border-radius: 0 0 8px 8px;
  background: transparent;
  resize: none;
}

button {
  position: relative;
  z-index: 1;
  margin-right: -8px;
  border: 0;
  border-radius: 0 0 8px 8px;
  background: #37b35a;
  cursor: pointer;
}
<form id="chat">
  <fieldset class="frame">
    <fieldset id="scroller">
      <fieldset id="box"></fieldset>
    </fieldset>
    <fieldset class="control">
      <textarea id="message" placeholder="Type your message here..."></textarea>
      <button>Send</button>
    </fieldset>
  </fieldset>
</form>

本文标签: javascriptHow do I resize two flexboxes in an animationStack Overflow