Problems with foundation 6 grid on img overlay in react - javascript

I am using the foundation 6 xy grid for the first time and am having difficulties aligning three images which have a text overlay at the bottom of each image. I struggling to get the background of the text to fill the full width of the image while making it responsive, 100% width of the newsArticle__pCont class is greater than the parent cell which I don't understand. Below is the closest that I have got (which isn't very close)
class News extends Component {
renderArticlePreview(article) {
if(articleCount <= 2) {
articleCount++;
return (
<div key={article.id} className="cell small-12 medium-4 newsArticle__cont--firstThree">
<img className="newsArticle__img--overlay" src={article.imageUrl} />
<div className="newsArticle__pCont">
{article.title}</p>
</div>
</div>
)
}
}
render() {
const { news } = this.props;
return (
<div>
<Header />
<div className="grid-container">
<div className="grid-x grid-padding-x newsArticle">
{ news.map((article) => this.renderArticlePreview(article)) }
</div>
</div>
</div>
)
}
}
export default News;
.scss
#import './Helpers.scss';
.newsArticle {
.newsArticle__cont--firstThree {
position: relative;
}
.newsArticle__title {
text-align: center;
font-size: 24px;
font-weight: 500;
color: #000000;
}
.newsArticle__title--overlay {
font-size: 16px;
font-weight: 500;
color: #FFFFFF;
text-align: center;
margin-bottom: 0px;
}
.newsArticle__pCont {
background-color: rgba(0, 94, 154, 0.75);
height: 45px;
position: absolute;
bottom: 0px;
z-index: 10;
width: 100%;
}
#include screen(sm-only) {
.newsArticle__img {
margin-top: 10px;
}
}
#include screen(md) {
.newsArticle__img {
margin: 10px;
}
.newsArticle__p {
margin: 10px;
}
}
}

So the reason your text right now is too large is because it is
position absolute (which takes it out of the flow, so it's not being
contained by the padding of the cell itself)
width: 100%, which will take it to the full width of the parent (which includes the padding).
The simplest way to fix this in your case would be to switch from padding-x (gutters are actually inside the cell, cell width includes the gutters) to margin-x (gutters are done by margin, so cell width excludes gutters). This would change your grid to look like:
<div className="grid-x grid-margin-x newsArticle">
A codepen showing this: https://codepen.io/kball/pen/zEGoaR

Related

How to fade out the very right of a navbar and add a new button to it

I want a feature on my Navbar where if my Navbar's width is greater than its parent div, fade the very right of it and add a new button to it. Something similar to Youtube.
1- When the width of Navbar is greater than its parents div it looks like this:
2- When the width of Navbar is less than its parents div it looks like this:
How can I achieve this?
Also this is my Navbar:
<template>
<nav class="navbar navbar-expand-lg navbar-light bg-light pl-4">
<form class="form-inline" v-for="(genre, index) in genres" :key="index">
<button class="btn btn-outline-dark m-1" type="button">
{{ genre }}
</button>
</form>
</nav>
</template>
<script>
export default {
data() {
return {
genres: [
.
.
.
"REALITY TV",
"SPORTS",
"HORROR"
]
};
}
};
</script>
So, what you need to do (also what I think youtube is doing):
Have a container(div) to keep all the buttons in them, with overflow hidden, to disallow scroll. Have a parent div to house the container div.
Have two absolutely positioned arrow buttons on left and right of the buttons container.
Scroll the container manually using css transform: translateX(0px).
Change the value of px to translate on click of those arrow buttons.
Calculate the size of the container div, and parent div, calculate values of max, min scroll. While scrolling make sure value of translateX stays within bounds to avoid overscolling. Also show hide your arrow buttons too according to these values.
Here is a codesandbox with working demo.
Also adding the code below:
<template>
<div>
<nav class="navbar" ref="navbar">
<div class="left-arrow" v-if="translateX < 0">
<div class="left-arrow-button" v-on:click="moveLeft"><</div>
</div>
<div class="scroll-container" ref="scroll" :style="scrollStyle">
<div class="btn" v-for="(genre, index) in genres" :key="index">
{{ genre }}
</div>
</div>
<div class="right-arrow" v-if="translateX > minVal">
<div class="right-arrow-button" v-on:click="moveRight">></div>
</div>
</nav>
</div>
</template>
<script>
export default {
data() {
return {
genres: [
"GENRE 1",
"GENRE 2",
"GENRE 3",
"GENRE 4",
"GENRE 5",
"GENRE 6",
"GENRE 7",
"GENRE 8",
"REALITY TV",
"SPORTS",
"HORROR",
],
scrollWidth: 0,
navbarWidth: 0,
translateX: 0,
};
},
computed: {
scrollStyle: function () {
return {
transform: `translateX(${this.translateX}px)`,
};
},
minVal: function () {
return -(this.scrollWidth - this.navbarWidth);
},
},
mounted() {
this.getScrollWidth();
},
methods: {
getScrollWidth() {
this.scrollWidth = this.$refs.scroll.clientWidth;
this.navbarWidth = this.$refs.navbar.clientWidth;
// console.log(this.scrollWidth, this.navbarWidth);
},
moveRight() {
const newVal = this.translateX - this.navbarWidth / 2;
this.translateX = Math.max(this.minVal, newVal);
},
moveLeft() {
const newVal = this.translateX + this.navbarWidth / 2;
this.translateX = Math.min(0, newVal);
},
},
};
</script>
<style scoped>
.navbar {
display: flex;
flex-direction: row;
overflow: hidden;
position: relative;
/* width: 500px; */
}
.scroll-container {
display: flex;
}
.btn {
margin: 0 8px;
padding: 8px 16px;
background-color: grey;
border-radius: 16px;
}
.left-arrow {
position: absolute;
left: 0;
z-index: 1000;
height: 100%;
display: flex;
}
.left-arrow::after {
background: linear-gradient(to right, white 20%, rgba(255, 255, 255, 0) 80%);
height: 100%;
width: 50px;
content: "";
pointer-events: none;
}
.left-arrow-button {
display: flex;
align-items: center;
background-color: white;
cursor: pointer;
}
.right-arrow {
position: absolute;
right: 0;
z-index: 1000;
height: 100%;
display: flex;
}
.right-arrow::before {
background: linear-gradient(to left, white 20%, rgba(255, 255, 255, 0) 80%);
height: 100%;
width: 50px;
content: "";
pointer-events: none;
}
.right-arrow-button {
display: flex;
align-items: center;
background-color: white;
cursor: pointer;
}
</style>
I am using plain css and html tags, if you are using bootstrap or something like that for styling, you can easily update the code make use of that styling.
Also I have not implemented any animations to make the scrolling smooth, which I think youtube does. To keep the answer short and focused on a single problem, I have skipped that. You can try doing that yourself and if you face any problems, please ask a separate question for that.

My script is not showing whats truly visible element wise

I found posts and online articles on how to do something like this but most examples are not in plain JavaScript. So this script works almost perfectly if all the sections are the same height for example 220px. So I thought I was getting closer in having this script working how I want it to work like overtime but then I realize
it had flaws when I decided to change the sections height and play around with the code more to see if it had any flaws that I was unaware of so basically this script is designed to output the name
of the sections that are visible but it is not showing the correct output for example if section 1 is the only one that is visible in the div it will say section-1 if multiple sections are visible it will say for example section-1,section-2 etc. Basically it should work like this regardless of the sections height
I know I have to change the code or altered it but I'm getting more confused the more I play around with it so how can I pull this off so I can always have the correct output? If I have to change my code completely to be able to do this then I don't mind.
This is my code
document.addEventListener('DOMContentLoaded',function(){
document.querySelector('#building').addEventListener('scroll',whichSectionsAreInSight);
function whichSectionsAreInSight(){
var building= document.querySelector('#building');
var top = building.scrollTop;
var bottom = top+building.offsetHeight;
var arr = [];
Array.prototype.forEach.call(
building.querySelectorAll('#building .sections'),
function(sections){
if ((sections.offsetTop < top && top <sections.offsetTop+sections.offsetHeight) || (sections.offsetTop < bottom && bottom < sections.offsetTop+sections.offsetHeight)){
arr.push(sections.id);
}
}
);
document.querySelector('#status').innerHTML = arr.join(',')
}
whichSectionsAreInSight();
});
h1{
margin: 0;
text-align: center;
}
#building{
background-color: gray;
height: 200px;
width: 200px;
overflow: auto;
}
.sections{
height: 80px;
width: 100%;
}
#section-1{
background-color: dodgerblue;
}
#section-2{
background-color: gold;
}
#section-3{
background-color: red;
}
#section-4{
background-color: gray;
height: 220px;
}
<p id='status'></p>
<div id='building'>
<div id='section-1' class='sections'><h1>Section 1</h1></div>
<div id='section-2' class='sections'><h1>Section 2</h1></div>
<div id='section-3' class='sections'><h1>Section 3</h1></div>
<div id='section-4' class='sections'><h1>Section 4</h1></div>
</div>
You were pretty close!
First off, you need to set the parent element to position:relative otherwise the parent that is being measured against is the document.
Also, the algorithm is simpler than what you had. Just make sure that the top of the element is less than the bottom of the parent, and the bottom of the element is greater than the top of the parent.
In your case this is offsetTop < bottom and offsetTop + offsetHeight > top
document.addEventListener('DOMContentLoaded', function() {
document.querySelector('#building').addEventListener('scroll', whichSectionsAreInSight);
function whichSectionsAreInSight() {
var building = document.querySelector('#building');
var top = building.scrollTop;
var bottom = top + building.offsetHeight;
var arr = [];
Array.prototype.forEach.call(
building.querySelectorAll('#building .sections'),
function(section) {
if (section.offsetTop < bottom && section.offsetTop + section.offsetHeight > top) {
arr.push(section.id);
}
}
);
document.querySelector('#status').innerHTML = arr.join(',')
}
whichSectionsAreInSight();
});
h1 {
margin: 0;
text-align: center;
}
#building {
background-color: gray;
height: 200px;
width: 200px;
overflow: auto;
position: relative;
}
.sections {
height: 80px;
width: 100%;
}
#section-1 {
background-color: dodgerblue;
}
#section-2 {
background-color: gold;
}
#section-3 {
background-color: red;
}
#section-4 {
background-color: gray;
height: 220px;
}
<p id='status'></p>
<div id='building'>
<div id='section-1' class='sections'>
<h1>Section 1</h1>
</div>
<div id='section-2' class='sections'>
<h1>Section 2</h1>
</div>
<div id='section-3' class='sections'>
<h1>Section 3</h1>
</div>
<div id='section-4' class='sections'>
<h1>Section 4</h1>
</div>
</div>

Position coordinates of an element related to viewport (not relative) via JS

I am building a vueJs web-app and I need the position of an element inside my web-app according to the viewport (not to the relative parent element). I wonder if there is a function/property doing this.
.offsetLeft is not what I need, because the element is inside of parent-elements with position: relative.
Please check out my pen: https://codepen.io/mister-hansen/pen/aGdWMp
(With an example what different position: relative makes.)
HTML
<div id="app">
<div class="box">
<div class="box__in" ref="my_box_a">
What is my position?
<br> offsetLeft: <strong>{{posBoxA}}</strong>
</div>
</div>
<div class="box box--relative">
<div class="box__in" ref="my_box_b">
What is my position in relative box?
<br>
offsetLeft: <strong>{{posBoxB}}?!</strong>
</div>
</div>
</div>
JS - VueJs
const app = new Vue({
el: '#app',
data () {
return {
posBoxA: 0,
posBoxB: 0,
}
},
mounted () {
this.calcPosOfBox()
},
methods: {
calcPosOfBox () {
this.posBoxA = this.$refs['my_box_a'].offsetLeft
this.posBoxB = this.$refs['my_box_b'].offsetLeft
}
}
})
SCSS
html, body {
margin: 0;
}
#app {
padding: 10vh 100px;
font-family: sans-serif;
}
.box {
margin: 0 auto 10vh;
padding: 10vh 50px;
background: lightgrey;
&--relative {
border: 1px solid red;
position: relative;
}
&__in {
padding: 1rem;
background: lightgreen;
}
}
Use getBoundingClientRect(). The x and y returns are relative to the top-level viewport.
Emphasis mine:
The returned value is a DOMRect object which is the union of the
rectangles returned by getClientRects() for the element, i.e., the CSS
border-boxes associated with the element. The result is the smallest
rectangle which contains the entire element, with read-only left, top,
right, bottom, x, y, width, and height properties describing the
overall border-box in pixels. Properties other than width and height
are relative to the top-left of the viewport.
const app = new Vue({
el: '#app',
data() {
return {
posBoxA: 0,
posBoxB: 0,
}
},
mounted() {
this.calcPosOfBox()
},
methods: {
calcPosOfBox() {
const boxABB = this.$refs["my_box_a"].getBoundingClientRect();
const boxBBB = this.$refs["my_box_b"].getBoundingClientRect();
this.posBoxA = boxABB.x;
this.posBoxB = boxBBB.x;
}
}
})
html,
body {
margin: 0;
}
#app {
padding: 10vh 100px;
font-family: sans-serif;
}
.box {
margin: 0 auto 10vh;
padding: 10vh 50px;
background: lightgrey;
}
.box--relative {
border: 1px solid red;
position: relative;
}
.box__in {
padding: 1rem;
background: lightgreen;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.min.js"></script>
<div id="app">
<div class="box">
<div class="box__in" ref="my_box_a">
What is my position?
<br> offsetLeft: <strong>{{posBoxA}}</strong>
</div>
</div>
<div class="box box--relative">
<div class="box__in" ref="my_box_b">
What is my position in relative box?
<br> offsetLeft: <strong>{{posBoxB}}?!</strong>
</div>
</div>
</div>

Scrollable div to stick to bottom, when outer div changes in size

Here is an example chat app ->
The idea here is to have the .messages-container take up as much of the screen as it can. Within .messages-container, .scroll holds the list of messages, and in case there are more messages then the size of the screen, scrolls.
Now, consider this case:
The user scrolls to the bottom of the conversation
The .text-input, dynamically gets bigger
Now, instead of the user staying scrolled to the bottom of the conversation, the text-input increases, and they no longer see the bottom.
One way to fix it, if we are using react, calculate the height of text-input, and if anything changes, let .messages-container know
componentDidUpdate() {
window.setTimeout(_ => {
const newHeight = this.calcHeight();
if (newHeight !== this._oldHeight) {
this.props.onResize();
}
this._oldHeight = newHeight;
});
}
But, this causes visible performance issues, and it's sad to be passing messages around like this.
Is there a better way? Could I use css in such a way, to express that when .text-input-increases, I want to essentially shift up all of .messages-container
2:nd revision of this answer
Your friend here is flex-direction: column-reverse; which does all you ask while align the messages at the bottom of the message container, just like for example Skype and many other chat apps do.
.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%; }
The downside with flex-direction: column-reverse; is a bug in IE/Edge/Firefox, where the scrollbar doesn't show, which your can read more about here: Flexbox column-reverse and overflow in Firefox/IE
The upside is you have ~ 90% browser support on mobile/tablets and ~ 65% for desktop, and counting as the bug gets fixed, ...and there is a workaround.
// 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));
}
In the below code snippet I've added the 2 functions from above, to make IE/Edge/Firefox behave in the same way flex-direction: column-reverse; does.
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>
Side note 1: The detection method is not fully tested, but it should work on newer browsers.
Side note 2: Attach a resize event handler for the chat-input might be more efficient then calling the updateScroll function.
Note: Credits to HaZardouS for reusing his html structure
You just need one CSS rule set:
.messages-container, .scroll {transform: scale(1,-1);}
That's it, you're done!
How it works: First, it vertically flips the container element so that the top becomes the bottom (giving us the desired scroll orientation), then it flips the content element so that the messages won't be upside down.
This approach works in all modern browsers. It does have a strange side effect, though: when you use a mouse wheel in the message box, the scroll direction is reversed. This can be fixed with a few lines of JavaScript, as shown below.
Here's a demo and a fiddle to play with:
//Reverse wheel direction
document.querySelector('.messages-container').addEventListener('wheel', function(e) {
if(e.deltaY) {
e.preventDefault();
e.currentTarget.scrollTop -= e.deltaY;
}
});
//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<p>Message 11<p>Message 12<p>Message 13<p>Message 14<p>Message 15<p>Message 16<p>Message 17<p>Message 18<p>Message 19<p>Message 20
</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>
Edit: thanks to #SomeoneSpecial for suggesting a simplification to the scroll code!
Please try the following fiddle - https://jsfiddle.net/Hazardous/bypxg25c/. Although the fiddle is currently using jQuery to grow/resize the text area, the crux is in the flex related styles used for the messages-container and input-container classes -
.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;
}
The flex-shrink value is set to 1 for .messages-container and 0 for .input-container. This ensures that messages-container shrinks when there is a reallocation of size.
I've moved text-input within messages, absolute positioned it to the bottom of the container and given messages enough bottom padding to space accordingly.
Run some code to add a class to conversation, which changes the height of text-input and bottom padding of messages using a nice CSS transition animation.
The JavaScript runs a "scrollTo" function at the same time as the CSS transition is running to keep the scroll at the bottom.
When the scroll comes off the bottom again, we remove the class from conversation
Hope this helps.
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>
You write;
Now, consider this case:
The user scrolls to the bottom of the conversation
The .text-input, dynamically gets bigger
Wouldn't the method that dynamically sets the .text-input be the logical place to fire this.props.onResize().
To whom it may concern,
The answers above did not suffice my question.
The solution I found was to make my innerWidth and innerHeight variable constant - as the innerWidth of the browser changes on scroll to adapt for the scrollbar.
var innerWidth = window.innerWidth
var innerHeight = window.innerHeight
OR FOR REACT
this.setState({width: window.innerWidth, height: window.innerHeight})
In other words, to ignore it, you must make everything constant as if it were never scrolling. Do remember to update these on Resize / Orientation Change !
IMHO current answer is not a correct one:
1/ flex-direction: column-reverse; reverses the order of messages - I didn't want that.
2/ javascript there is also a bit hacky and obsolete
If you want to make it like a PRO use spacer-box which has properties:
flex-grow: 1;
flex-basis: 0;
and is located above messages. It pushes them down to the chat input.
When user is typing new messages and input height is growing the scrollbar moves up, but when the message is sent (input is cleared) scrollbar is back at bottom.
Check my snippet:
body {
background: #ccc;
}
.chat {
display: flex;
flex-direction: column;
width: 300px;
max-height: 300px;
max-width: 90%;
background: #fff;
}
.spacer-box {
flex-basis: 0;
flex-grow: 1;
}
.messages {
display: flex;
flex-direction: column;
overflow-y: auto;
flex-grow: 1;
padding: 24px 24px 4px;
}
.footer {
padding: 4px 24px 24px;
}
#chat-input {
width: 100%;
max-height: 100px;
overflow-y: auto;
border: 1px solid pink;
outline: none;
user-select: text;
white-space: pre-wrap;
overflow-wrap: break-word;
}
<div class="chat">
<div class="messages">
<div class="spacer-box"></div>
<div class="message">1</div>
<div class="message">2</div>
<div class="message">3</div>
<div class="message">4</div>
<div class="message">5</div>
<div class="message">6</div>
<div class="message">7</div>
<div class="message">8</div>
<div class="message">9</div>
<div class="message">10</div>
<div class="message">11</div>
<div class="message">12</div>
<div class="message">13</div>
<div class="message">14</div>
<div class="message">15</div>
<div class="message">16</div>
<div class="message">17</div>
<div class="message">18</div>
</div>
<div class="footer">
<div contenteditable role="textbox" id="chat-input"></div>
</div>
<div>
Hope I could help :)
Cheers

Mobile slider only responding every other touch

I am trying to build a slider based upon http://css-tricks.com/the-javascript-behind-touch-friendly-sliders/. My goal is to make a horizontal, mobile-only slider that allows you to slide back and forth between the steps in a registration process.
The code works for the most part, but the slider only moves every other touch, and I'm not sure why.
http://codepen.io/anon/pen/zKhao
HTML:
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<div class="visible-xs mobile-tabs">
<div class="slider-wrap">
<div class="slider" id="slider">
<div class="holder">
<div class="slide-wrapper">
<h4 class="complete">Before you begin</h4>
</div>
<div class="slide-wrapper">
<h4 class="complete">1. Terms & Conditions</h4>
</div>
<div class="slide-wrapper">
<h4 class="current">2. Teams</h4>
</div>
<div class="slide-wrapper">
<h4>3. Add-Ons</h4>
</div>
<div class="slide-wrapper">
<h4>4. Review & Submit</h4>
</div>
</div>
</div>
</div>
</div>
CSS:
a
{
color: #5fa4db;
text-decoration: none;
}
.mobile-tabs
{
height: 45px;
overflow: hidden;
border-bottom: 1px solid #f1f2f4;
white-space: nowrap;
margin-bottom: 10px;
}
.mobile-tabs h4
{
color: #9fa9b2;
display: inline-block;
padding-right: 10px;
padding-bottom: 10px;
font-weight: bold;
font-size: 18px;
}
.mobile-tabs h4.current
{
border-bottom: 5px solid #5fa4db;
color: #0f2034;
}
.mobile-tabs h4.complete
{
color: #5fa4db;
}
/* CSS for mobile tab slider.
Source: http://css-tricks.com/the-javascript-behind-touch-friendly-sliders/
*/
.mobile-tabs .animate {
transition: transform 0.3s ease-out;
}
.mobile-tabs .slider-wrap {
width: 100%;
position: absolute;
}
.mobile-tabs .slider {
width: 100%;
height: 100%;
overflow: hidden;
}
.mobile-tabs .ms-touch.slider {
overflow-x: scroll;
overflow-y: hidden;
-ms-overflow-style: none;
/* Hides the scrollbar. */
-ms-scroll-chaining: none;
/* Prevents Metro from swiping to the next tab or app. */
-ms-scroll-snap-type: mandatory;
/* Forces a snap scroll behavior on your images. */
-ms-scroll-snap-points-x: snapInterval(0%, 1%);
/* Defines the y and x intervals to snap to when scrolling. */
}
.mobile-tabs .holder {
width: 300%;
overflow-y: hidden;
}
.mobile-tabs .slide-wrapper {
float: left;
position: relative;
overflow: hidden;
}
.mobile-tabs .slide div {
width: 300px;
height: 500px;
z-index: 0;
}
JavaScript:
if (navigator.msMaxTouchPoints) {
$('#slider').addClass('ms-touch');
$('#slider').on('scroll', function () {
$('.slide-image').css('transform', 'translate3d(-' + (100 - $(this).scrollLeft() / 6) + 'px,0,0)');
});
} else {
var slider = {
el: {
slider: $("#slider"),
holder: $(".holder")
},
slideWidth: $('#slider').width(),
touchstartx: undefined,
touchmovex: undefined,
movex: 0,
index: 0,
longTouch: undefined,
init: function () {
this.bindUIEvents();
},
bindUIEvents: function () {
this.el.holder.on("touchstart", function (event) {
slider.start(event);
});
this.el.holder.on("touchmove", function (event) {
slider.move(event);
});
this.el.holder.on("touchend", function (event) {
slider.end(event);
});
},
start: function (event) {
// Test for flick.
this.longTouch = false;
setTimeout(function () {
window.slider.longTouch = true;
}, 250);
// Get the original touch position.
this.oldx = this.movex;
// The movement gets all janky if there's a transition on the elements.
$('.animate').removeClass('animate');
},
move: function (event) {
// Continuously return touch position.
this.touchmovex = event.originalEvent.touches[0].pageX;
// Calculate distance to translate holder.
this.movex = -this.oldx - this.touchmovex;
// Defines the speed the images should move at.
var panx = 100 - this.movex / 6;
if (this.movex < 600) { // Makes the holder stop moving when there is no more content.
this.el.holder.css('transform', 'translate3d(-' + this.movex + 'px,0,0)');
}
},
end: function (event) {
}
};
slider.init();
}
In order to emulate the issue, you'll have to view the code on a mobile device (or use Chrome's mobile emulation) and try to slide the slider back and forth. It will move, but only every other time you attempt to slide it.
I am completely lost, and any help will be appreciated.
This isn't really an answer, per se, but I've decided to throw the entire thing out and use jquery UI's Draggable feature to do what I need to do.
http://jqueryui.com/draggable/#constrain-movement

Categories