Remi Sture
Remi Sture

Reputation: 12978

With flexbox, how to change order and direction across breakpoints?

I'm trying to place the output element above the slider for smaller screens, and to the right of the "+" button for the rest. How can I do this with flexbox, without duplicating markup? Changing the order with CSS seems to not have the wanted effect when I have a wrapper div, and without the wrapper div, I can't align the output element above the slider.

Any ideas?

Desired output on smaller screens

$("#minus").click(function(event) {
  zoom("out");
});

$("#plus").click(function(event) {
  zoom("in");
});

$("#range").on('input change', function(event) {
  $('#output').text($(event.currentTarget).val());
});

function zoom(direction) {
  var slider = $("#range");
  var step = parseInt(slider.attr('step'), 10);
  var currentSliderValue = parseInt(slider.val(), 10);
  var newStepValue = currentSliderValue + step;

  if (direction === "out") {
    newStepValue = currentSliderValue - step;
  } else {
    newStepValue = currentSliderValue + step;
  }

  slider.val(newStepValue).change();
};
.container {
  width: 100%;
  margin: 50px auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

input[type=range] {
  width: 100%;
}

button {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 100%;
  background: white;
  font-size: 24px;
  border: 1px solid lightgrey;
  cursor: pointer;
  -webkit-appearance: none;
  margin: 0 10px;
}

.inner-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}

#minus {
  order: 1;
}

#range {
  order: 1;
}

#output {
  order: 1;
  @media screen and (min-width: 320px) {
    order: 5;
  }
}

#plus {
  order: 4;
}

.inner-wrap {
  order: 2;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
  <button id="minus">-</button>
  <div class="inner-wrap">
    <output for="range" id="output">50</output>
    <input id="range" type="range" step="10" value="50">
  </div>
  <button id="plus">+</button>
</div>

Desired output on larger screens

$("#minus").click(function(event) {
  zoom("out");
});

$("#plus").click(function(event) {
  zoom("in");
});

$("#range").on('input change', function(event) {
  $('#output').text($(event.currentTarget).val());
});

function zoom(direction) {
  var slider = $("#range");
  var step = parseInt(slider.attr('step'), 10);
  var currentSliderValue = parseInt(slider.val(), 10);
  var newStepValue = currentSliderValue + step;

  if (direction === "out") {
    newStepValue = currentSliderValue - step;
  } else {
    newStepValue = currentSliderValue + step;
  }

  slider.val(newStepValue).change();
};
.container {
  width: 100%;
  margin: 50px auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

input[type=range] {
  width: 100%;
}

button {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 100%;
  background: white;
  font-size: 24px;
  border: 1px solid lightgrey;
  cursor: pointer;
  -webkit-appearance: none;
  margin: 0 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
  <button id="minus">-</button>
  <input id="range" type="range" step="10" value="50">
  <button id="plus">+</button>
  <output for="range" id="output">50</output>
</div>

Upvotes: 4

Views: 769

Answers (1)

Michael Benjamin
Michael Benjamin

Reputation: 371221

Instead of using the order property and nested containers, use absolute positioning. Here's a simplified version of your code. The media query uses absolute positioning to center the output element in smaller screens.

revised demo

.container {
  display: flex;
  align-items: center;
  margin: 50px auto;
  position: relative;
}

input[type=range] {
  flex: 1;
  min-width: 0;
}

button {
  flex: 0 0 40px;
  height: 40px;
  border-radius: 100%;
  background: white;
  font-size: 24px;
  border: 1px solid lightgrey;
  cursor: pointer;
  margin: 0 10px;
}

@media (max-width: 320px) {
  #output {
    position: absolute;
    top: -15px;
    left: 50%;
    transform: translateX(-50%);
  }
}
<div class="container">
  <button id="minus">-</button>
  <input id="range" type="range" step="10" value="50">
  <button id="plus">+</button>
  <output for="range" id="output">50</output>
</div>


For others who may be interested, here's an alternative: Put the output element in both locations.

Control their appearance with display: none in the media query.

Something like this:

@media screen and (min-width: 320px) {
    #output-small-screen { display: none }
}
@media screen and (max-width: 320px) {
    #output-wide-screen { display: none }
}

revised demo

.container {
  width: 100%;
  margin: 50px auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

input[type=range] {
  width: 100%;
}

button {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 100%;
  background: white;
  font-size: 24px;
  border: 1px solid lightgrey;
  cursor: pointer;
  -webkit-appearance: none;
  margin: 0 10px;
}

.inner-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}

#minus {
  order: 1;
}

#range {
  order: 1;
}

#output-wide-screen {
  order: 5;
}

@media screen and (min-width: 320px) {
  #output-small-screen {
    display: none
  }
}

@media screen and (max-width: 320px) {
  #output-wide-screen {
    display: none
  }
}

#plus {
  order: 4;
}

.inner-wrap {
  order: 2;
}
<div class="container">
  <button id="minus">-</button>
  <div class="inner-wrap">
    <output for="range" class="output" id="output-small-screen">50</output>
    <input id="range" type="range" step="10" value="50">
  </div>
  <button id="plus">+</button>
  <output for="range" class="output" id="output-wide-screen">50</output>
</div>

Upvotes: 2

Related Questions