23 votos

Div desplazable para pegarse a la parte inferior, cuando div externo cambia de tamaño

Aquí es un ejemplo de chat de la aplicación ->



La idea aquí es tener la .messages-container ocupan tanto de la pantalla como se puede. Dentro de .messages-container, .scroll contiene la lista de mensajes, y en caso de que haya más mensajes, a continuación, el tamaño de la pantalla, se desplaza.

Ahora, considere la posibilidad de este caso:

  1. El usuario se desplaza a la parte inferior de la conversación
  2. El .text-input, de forma dinámica se hace más grande

Ahora, en lugar de que el usuario permanecer desplaza a la parte inferior de la conversación, el texto de entrada aumenta, y ya no se vea el fondo.

Una forma de solucionarlo, si estamos utilizando reaccionar, calcular la altura de texto de entrada, y si nada cambia, vamos .mensajes-contenedor saber

componentDidUpdate() {
  window.setTimeout(_ => {
    const newHeight = this.calcHeight();
    if (newHeight !== this._oldHeight) {
      this.props.onResize();
    }
    this._oldHeight = newHeight;
  });
}

Pero, esto hace visibles los problemas de rendimiento, y es triste estar pasando mensajes como este.

Hay una manera mejor? Podría utilizar css de tal manera, para expresar que cuando .texto de entrada aumenta, quiero esencialmente shift up a todos .mensajes de contenedor

6voto

LGSon Puntos 1306

2:nd revisión de esta respuesta

Tu amigo aquí es flex-direction: column-reverse; que hace todo lo que tú pides, mientras que alinear los mensajes en la parte inferior del mensaje de contenedor, como por ejemplo Skype y muchas otras apps de chat de hacer.

.chat-window{
  display:flex;
  flex-direction:column;
  height:100%;
}
.chat-messages{
  flex: 1;
  height:100%;
  overflow: auto;
  display: flex;
  flex-direction: column-reverse;
}

.chat-input { border-top: 1px solid #999; padding: 20px 5px }
.chat-input-text { width: 60%; min-height: 40px; max-width: 60%; }

La desventaja con flex-direction: column-reverse; es un error en IE/Edge/Firefox, donde la barra de desplazamiento no show, el cual puedes leer más aquí: Flexbox columna de la inversa y desbordamiento en Firefox/IE

La ventaja es que usted tiene ~ 90% soporte de navegador en el móvil/tabletas y ~ 65% para el escritorio, y contando como el error se fija ...y no hay una solución.

// scroll to bottom
function updateScroll(el){
  el.scrollTop = el.scrollHeight;
}
// only shift-up if at bottom
function scrollAtBottom(el){
  return (el.scrollTop + 5 >= (el.scrollHeight - el.offsetHeight));
}

En el siguiente fragmento de código, he añadido el 2 funciones desde arriba, para hacer que IE/Edge/Firefox se comportan de la misma manera flex-direction: column-reverse; .

function addContent () {
  var msgdiv = document.getElementById('messages');
  var msgtxt = document.getElementById('inputs');
  var atbottom = scrollAtBottom(msgdiv);

  if (msgtxt.value.length > 0) {
    msgdiv.innerHTML += msgtxt.value + '<br/>';
    msgtxt.value = "";
  } else {
    msgdiv.innerHTML += 'Long long content ' + (tempCounter++) + '!<br/>';
  }
  
  /* if at bottom and is IE/Edge/Firefox */
  if (atbottom && (!isWebkit || isEdge)) {
    updateScroll(msgdiv);
  }
}

function resizeInput () {
  var msgdiv = document.getElementById('messages');
  var msgtxt = document.getElementById('inputs');
  var atbottom = scrollAtBottom(msgdiv);

  if (msgtxt.style.height == '120px') {
    msgtxt.style.height = 'auto';
  } else {
    msgtxt.style.height = '120px';
  }
  
  /* if at bottom and is IE/Edge/Firefox */
  if (atbottom && (!isWebkit || isEdge)) {
    updateScroll(msgdiv);
  }
}


/* fix for IE/Edge/Firefox */
var isWebkit = ('WebkitAppearance' in document.documentElement.style);
var isEdge = ('-ms-accelerator' in document.documentElement.style);
var tempCounter = 6;

function updateScroll(el){
  el.scrollTop = el.scrollHeight;
}
function scrollAtBottom(el){
  return (el.scrollTop + 5 >= (el.scrollHeight - el.offsetHeight));
}
html, body { height:100%; margin:0; padding:0; }

.chat-window{
  display:flex;
  flex-direction:column;
  height:100%;
}
.chat-messages{
  flex: 1;
  height:100%;
  overflow: auto;
  display: flex;
  flex-direction: column-reverse;
}

.chat-input { border-top: 1px solid #999; padding: 20px 5px }
.chat-input-text { width: 60%; min-height: 40px; max-width: 60%; }


/* temp. buttons for demo */
button { width: 12%; height: 44px; margin-left: 5%; vertical-align: top; }

/* begin - fix for hidden scrollbar in IE/Edge/Firefox */
.chat-messages-text{ overflow: auto; }
@media screen and (-webkit-min-device-pixel-ratio:0) {
  .chat-messages-text{ overflow: visible; }
  /*  reset Edge as it identifies itself as webkit  */
  @supports (-ms-accelerator:true) { .chat-messages-text{ overflow: auto; } }
}
/* hide resize FF */
@-moz-document url-prefix() { .chat-input-text { resize: none } }
/* end - fix for hidden scrollbar in IE/Edge/Firefox */
<div class="chat-window">
  <div class="chat-messages">
    <div class="chat-messages-text" id="messages">
      Long long content 1!<br/>
      Long long content 2!<br/>
      Long long content 3!<br/>
      Long long content 4!<br/>
      Long long content 5!<br/>
    </div>
  </div>
  <div class="chat-input">
    <textarea class="chat-input-text" placeholder="Type your message here..." id="inputs"></textarea>
    <button onclick="addContent();">Add msg</button>
    <button onclick="resizeInput();">Resize input</button>
  </div>
</div>

Nota 1: El método de detección no está totalmente probado, pero debería funcionar en los navegadores más modernos.

Nota 2: Adjuntar un cambio de tamaño de controlador de eventos para el chat de entrada podría ser más eficiente, a continuación, llamar a la updateScroll función.

Nota: los Créditos a Peligrosos para la reutilización de su estructura html

6voto

DoctorDestructo Puntos 120

Usted sólo necesita un conjunto de reglas CSS:

.messages-container, .scroll {transform: scale(1,-1);}

Eso es todo, estás hecho! Que va a mantener la conversación se desplaza a la parte inferior, con el último mensaje en la vista, independientemente de cualquier cambio de tamaño del contenedor, caja de entrada, o cualquier otra cosa. Funciona en todos los navegadores modernos.

Cómo funciona: en Primer lugar, verticalmente volteretas en el exterior messages-container elemento (que controla el desplazamiento) de modo que la parte superior se convierte en la parte inferior. A continuación, se despliega el interior de la scroll elemento para que los mensajes no ser al revés.

Este enfoque tiene un extraño efecto secundario: cuando se utiliza la rueda del ratón (o de algo que emula uno) en el cuadro de mensaje, de la dirección de desplazamiento se invierte. Si eso le molesta, puedes solucionarlo con JavaScript (como se muestra a continuación).

He aquí una demostración y un jsfiddle para jugar con:

//Reverse wheel direction
document.querySelector('.messages-container').addEventListener('wheel', function(e) {
  if(e.deltaY) {
    e.preventDefault();
    e.currentTarget.scrollTop -= parseFloat(getComputedStyle(e.currentTarget).getPropertyValue('font-size')) * (e.deltaY < 0 ? -1 : 1) * 2;
  }
});

//The rest of the JS just handles the test buttons and is not part of the solution
send = function() {
  var inp = document.querySelector('.text-input');
  document.querySelector('.scroll').insertAdjacentHTML('beforeend', '<p>' + inp.value);
  inp.value = '';
  inp.focus();
}
resize = function() {
  var inp = document.querySelector('.text-input');
  inp.style.height = inp.style.height === '50%' ? null : '50%';
}
html,body {height: 100%;margin: 0;}
.conversation {
  display: flex;
  flex-direction: column;
  height: 100%;
}
.messages-container {
  flex-shrink: 10;
  height: 100%;
  overflow: auto;
}
.messages-container, .scroll {transform: scale(1,-1);}
.text-input {resize: vertical;}
<div class="conversation">
  <div class="messages-container">
    <div class="scroll">
      <p>Message 1<p>Message 2<p>Message 3<p>Message 4<p>Message 5
      <p>Message 6<p>Message 7<p>Message 8<p>Message 9<p>Message 10
    </div>
  </div>
  <textarea class="text-input" autofocus>Your message</textarea>
  <div>
    <button id="send" onclick="send();">Send input</button>
    <button id="resize" onclick="resize();">Resize input box</button>
  </div>
</div>

1voto

HazardouS Puntos 1019

Por favor, intente lo siguiente violín - https://jsfiddle.net/Hazardous/bypxg25c/. Aunque el violín es en la actualidad el uso de jQuery para crecer/cambiar el tamaño del área de texto, el quid está en el flex estilos relacionados utilizado para los mensajes de contenedor y de entrada las clases de contenedor -

.messages-container{
  order:1;
  flex:0.9 1 auto;
  overflow-y:auto;
  display:flex;
  flex-direction:row;
  flex-wrap:nowrap;
  justify-content:flex-start;
  align-items:stretch;
  align-content:stretch;
}

.input-container{
  order:2;
  flex:0.1 0 auto;
}

El flex-reducir el valor se establece en 1 .mensajes de contenedor y 0 para .entrada de contenedor. Esto asegura que los mensajes del recipiente se reduce cuando hay una reasignación de tamaño.

1voto

Jamie Barker Puntos 610

Me he mudado text-input dentro messages, absoluta colocado a la parte inferior del recipiente y dado messages inferior suficientes de relleno en el espacio en consecuencia.

Ejecutar algún código para agregar una clase a conversation, lo que cambia la altura de text-input y el margen inferior de la messages el uso de un buen CSS animación de transición.

El código JavaScript se ejecuta un "desplázate de función", al mismo tiempo, como la transición de CSS se está ejecutando para mantener el desplazamiento en la parte inferior.

Cuando el desplazamiento sale de la parte inferior de nuevo, se elimina la clase de conversation

Espero que esto ayude.

https://jsfiddle.net/cnvzLfso/5/

var doScollCheck = true;
var objConv = document.querySelector('.conversation');
var objMessages = document.querySelector('.messages');
var objInput = document.querySelector('.text-input');

function scrollTo(element, to, duration) {
  if (duration <= 0) {
    doScollCheck = true;
    return;
  }
  var difference = to - element.scrollTop;
  var perTick = difference / duration * 10;

  setTimeout(function() {
    element.scrollTop = element.scrollTop + perTick;
    if (element.scrollTop === to) {
      doScollCheck = true;
      return;
    }
    scrollTo(element, to, duration - 10);
  }, 10);
}

function resizeInput(atBottom) {
  var className = 'bigger',
    hasClass;
  if (objConv.classList) {
    hasClass = objConv.classList.contains(className);
  } else {
    hasClass = new RegExp('(^| )' + className + '( |$)', 'gi').test(objConv.className);
  }
  if (atBottom) {
    if (!hasClass) {
      doScollCheck = false;
      if (objConv.classList) {
        objConv.classList.add(className);
      } else {
        objConv.className += ' ' + className;
      }
      scrollTo(objMessages, (objMessages.scrollHeight - objMessages.offsetHeight) + 50, 500);
    }
  } else {
    if (hasClass) {
      if (objConv.classList) {
        objConv.classList.remove(className);
      } else {
        objConv.className = objConv.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
      }
    }
  }
}

objMessages.addEventListener('scroll', function() {
  if (doScollCheck) {
    var isBottom = ((this.scrollHeight - this.offsetHeight) === this.scrollTop);
    resizeInput(isBottom);
  }
});
html,
body {
  height: 100%;
  width: 100%;
  background: white;
}
body {
  margin: 0;
  padding: 0;
}
.conversation {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: 100%;
  position: relative;
}
.messages {
  overflow-y: scroll;
  padding: 10px 10px 60px 10px;
  -webkit-transition: padding .5s;
  -moz-transition: padding .5s;
  transition: padding .5s;
}
.text-input {
  padding: 10px;
  -webkit-transition: height .5s;
  -moz-transition: height .5s;
  transition: height .5s;
  position: absolute;
  bottom: 0;
  height: 50px;
  background: white;
}
.conversation.bigger .messages {
  padding-bottom: 110px;
}
.conversation.bigger .text-input {
  height: 100px;
}
.text-input input {
  height: 100%;
}
<div class="conversation">
  <div class="messages">
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is the last message
    </p>
    <div class="text-input">
      <input type="text" />
    </div>
  </div>
</div>

0voto

Janaka Stevens Puntos 1714

Se escribe;

Now, consider this case:

    The user scrolls to the bottom of the conversation
    The .text-input, dynamically gets bigger

No sería el método que establece dinámicamente la entrada .text el lugar lógico para disparar this.props.onResize().

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X