如何防止伪元素或输入加宽弹性盒中的弹性项目?

How do I prevent a pseudo-element or input from widening a flex item in a flexbox?

我创建了一个 flexbox 容器,它充当具有两个输入字段的表单控件,代表两个日期之间的范围。如果用户在两个字段中的任何一个字段中输入了内容,则该字段旁边会出现一个叉号。用户可以点击这个十字来清除该字段。

十字架是一个 :after 伪元素,创建并与之交互,带有一些漂亮的 css 和 JavaScript,主要基于 this answer on Stack Overflow。这是我的实现(有些简化,但不多):

$('.field.clearable').on('change input', 'input', function($e) {
    $(this).parent().toggleClass('non-empty', !!this.value);
  })
  .on('mousemove', 'span.non-empty', function($e) {
    var $this = $(this);
    $this.toggleClass('onX', $this.width() < $e.clientX - $this.offset().left);
    $this.hasClass('onX') ? $this.attr('title', 'Clear field') : $this.removeAttr('title');
  })
  .on('mouseleave', 'span.non-empty', function($e) {
    var $this = $(this);
    $this.removeClass('onX')
      .removeAttr('title');
  })
  .on('click', 'span.non-empty.onX', function($e) {
    $e.preventDefault();
    var $this = $(this);
    $this.removeClass('non-empty onX')
      .removeAttr('title')
      .children('input')
      .val('')
      .trigger('clear');
  });
/* reset and cosmetic css */

* {
  margin: 0;
  padding: 0;
  color: inherit;
  font-family: inherit;
  font-size: inherit;
}

::-webkit-input-placeholder {
  color: #ccc;
  font-size: 80%;
  opacity: .8;
}

:-moz-placeholder {
  color: #ccc;
  font-size: 80%;
  opacity: .8;
}

::-moz-placeholder {
  color: #ccc;
  font-size: 80%;
  opacity: .8;
}

:-ms-input-placeholder {
  color: #ccc;
  font-size: 80%;
  opacity: .8;
}

:focus::-webkit-input-placeholder {
  opacity: .5;
}

:focus:-moz-placeholder {
  opacity: .5;
}

:focus::-moz-placeholder {
  opacity: .5;
}

:focus:-ms-input-placeholder {
  opacity: .5;
}

html {
  font-family: Arial;
  font-size: 10px;
  font-weight: normal;
  text-align: left;
  height: 100%;
}

body {
  margin: 4em auto;
  width: 400px;
  color: #2c5ba0;
  font-size: 1.5rem;
  line-height: 1.5em;
  background-color: #fff;
}


/* relevant css */

div.field {
  display: inline-block;
  margin: .4em 0;
}

div.field>label {
  display: block;
  margin: 0 0 .3em .2em;
  font-size: 80%;
  line-height: 1em;
}

div.field>span {
  position: relative;
  display: -webkit-flex;
  display: flex;
  -webkit-align-items: center;
  align-items: center;
  -webkit-justify-content: space-between;
  justify-content: space-between;
  box-sizing: border-box;
  height: 2em;
  border: .1em solid #ddd;
  background-color: #fff;
}

div.field>span>span {
  -webkit-flex: 1;
  flex: 1;
}

div.field>span>span>input {
  width: 100%;
  border: none;
}

div.field.range>span input {
  text-align: center;
}

div.field.range>span label {
  -webkit-flex: 0;
  flex: 0;
  margin: 0 .2em;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

div.field.clearable span.non-empty {
  position: relative;
  padding-right: 2em;
}

div.field.clearable span.non-empty:after {
  position: absolute;
  right: 0;
  top: 0;
  content: 'x';
  display: inline-block;
  width: 2em;
  height: 2em;
  color: #ddd;
  vertical-align: middle !important;
  text-align: center !important;
  font-style: normal !important;
  font-weight: normal !important;
  text-transform: none !important;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  cursor: pointer;
}

div.field.clearable span.non-empty.onX:after {
  color: #f37e31;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="field clearable range">
  <label for="start">created</label>
  <span>
    <span>
      <input type="text" id="start" placeholder="from">
    </span>
    <label for="end">–</label>
    <span>
      <input type="text" id="end" placeholder="until">
    </span>
  </span>
</div>

当两个 <input> 字段都包含值并因此在它们旁边有一个叉时,它们的宽度比率很好地相互抵消并且没有错。但是,当 <input> 字段中只有一个包含值并且叉号仅出现在 <input> 字段旁边时,叉号 / <input> 字段会将其他内容推到一边。

这不应该发生。我怀疑 <input> 字段是罪魁祸首。尽管容器 div.field 应保持其 display: block 灵活性,但内部内容物的比例应保持不变。

以下是我认为可以通过以下方式实现的相关部分:

/* the span that acts as the flexbox container */
div.field>span {
  position: relative;
  display: -webkit-flex;
  display: flex;
  -webkit-align-items: center;
  align-items: center;
  -webkit-justify-content: space-between;
  justify-content: space-between;
  box-sizing: border-box;
  height: 2em;
  border: .1em solid #ddd;
}

/* let the spans, containing the <input> fields, take available space */
div.field>span>span {
  -webkit-flex: 1;
  flex: 1;
}

/*
  let <input> fields take 100% content space of span

  I thought this should take into account any padding on its parent
*/
div.field>span>span>input {
  width: 100%;
  border: none;
}

/* let center label (the dash) not be flexible */
div.field.range>span label {
  -webkit-flex: 0;
  flex: 0;
  margin: 0 .2em;
}

/* if .non-empty class was set by JavaScript */

/*
  make room for :after by setting padding-right accordingly

  but this is the issue:
  I thought this would also make the <input> shrink accordingly,
  but it appears it doesn't shrink the amount I anticipated
*/
div.field.clearable span.non-empty {
  position: relative;
  padding-right: 2em;
}

/* show :after pseudo-element */
div.field.clearable span.non-empty:after {
  position: absolute;
  right: 0;
  top: 0;
  content: 'x';
  display: inline-block;
  width: 2em;
  height: 2em;
  /* etc. */
}

你知道我如何让完整的控制元素保持其灵活性,包括它的 :after 诡计,但当其中一个十字出现时,让 <input> 相应地缩小吗?

或者更简单地说,我想:有没有办法让两个 flex: 1 的弹性项目在任何给定时间都占用相同数量的 space?


我使用 :after 元素而不是 background-image 的原因是因为我使用的是自定义图标字体而不是图标图像。

如果您放下 padding-right 并调整 input 的大小,它将起作用

不过您需要修复点击处理程序

div.field.clearable span.non-empty {
  position: relative;
}

div.field.clearable span.non-empty input {
  width: calc(100% - 2em);
}

$('.field.clearable').on('change input', 'input', function($e) {
    $(this).parent().toggleClass('non-empty', !!this.value);
  })
  .on('mousemove', 'span.non-empty', function($e) {
    var $this = $(this);
    $this.toggleClass('onX', $this.width() < $e.clientX - $this.offset().left);
    $this.hasClass('onX') ? $this.attr('title', 'Clear field') : $this.removeAttr('title');
  })
  .on('mouseleave', 'span.non-empty', function($e) {
    var $this = $(this);
    $this.removeClass('onX')
      .removeAttr('title');
  })
  .on('click', 'span.non-empty.onX', function($e) {
    $e.preventDefault();
    var $this = $(this);
    $this.removeClass('non-empty onX')
      .removeAttr('title')
      .children('input')
      .val('')
      .trigger('clear');
  });
/* reset and cosmetic css */

* {
  margin: 0;
  padding: 0;
  color: inherit;
  font-family: inherit;
  font-size: inherit;
}

::-webkit-input-placeholder {
  color: #ccc;
  font-size: 80%;
  opacity: .8;
}

:-moz-placeholder {
  color: #ccc;
  font-size: 80%;
  opacity: .8;
}

::-moz-placeholder {
  color: #ccc;
  font-size: 80%;
  opacity: .8;
}

:-ms-input-placeholder {
  color: #ccc;
  font-size: 80%;
  opacity: .8;
}

:focus::-webkit-input-placeholder {
  opacity: .5;
}

:focus:-moz-placeholder {
  opacity: .5;
}

:focus::-moz-placeholder {
  opacity: .5;
}

:focus:-ms-input-placeholder {
  opacity: .5;
}

html {
  font-family: Arial;
  font-size: 10px;
  font-weight: normal;
  text-align: left;
  height: 100%;
}

body {
  margin: 4em auto;
  width: 400px;
  color: #2c5ba0;
  font-size: 1.5rem;
  line-height: 1.5em;
  background-color: #fff;
}


/* relevant css */

div.field {
  display: inline-block;
  margin: .4em 0;
}

div.field>label {
  display: block;
  margin: 0 0 .3em .2em;
  font-size: 80%;
  line-height: 1em;
}

div.field>span {
  position: relative;
  display: -webkit-flex;
  display: flex;
  -webkit-align-items: center;
  align-items: center;
  -webkit-justify-content: space-between;
  justify-content: space-between;
  box-sizing: border-box;
  height: 2em;
  border: .1em solid #ddd;
  background-color: #fff;
}

div.field>span>span {
  -webkit-flex: 1;
  flex: 1;
}

div.field>span>span>input {
  width: 100%;
  border: none;
}

div.field.range>span input {
  text-align: center;
}

div.field.range>span label {
  -webkit-flex: 0;
  flex: 0;
  margin: 0 .2em;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

div.field.clearable span.non-empty {
  position: relative;
}

div.field.clearable span.non-empty input {
  width: calc(100% - 2em);
}

div.field.clearable span.non-empty:after {
  position: absolute;
  right: 0;
  top: 0;
  content: 'x';
  display: inline-block;
  width: 2em;
  height: 2em;
  color: #ddd;
  vertical-align: middle !important;
  text-align: center !important;
  font-style: normal !important;
  font-weight: normal !important;
  text-transform: none !important;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  cursor: pointer;
}

div.field.clearable span.non-empty.onX:after {
  color: #f37e31;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="field clearable range">
  <label for="start">created</label>
  <span>
    <span>
      <input type="text" id="start" placeholder="from">
    </span>
    <label for="end">–</label>
    <span>
      <input type="text" id="end" placeholder="until">
    </span>
  </span>
</div>

However, when only one of the fields contains a value and the cross appears next to only that field, the cross / field nudges the other content aside.

This is not supposed to happen. I suspect the fields are the culprit. Even though the container div.field should keep its display: block flexibility, the ratios of the inner content should stay constant.

这是因为您的元素为关闭按钮欺骗而构建的方式。通常 span 上的 padding 会导致 flex 项目增大或减小其大小并影响 flex 布局。但是,如果您删除填充,那么您将很难计算点击关闭的偏移量。

此外,您必须继续调整宽度并计算鼠标光标在 mousemove 上的位置,以保持关闭按钮正常工作。

可能更好的解决方案之一是在 input 上使用填充,并且仅使用伪元素的位置和不透明度来计算关闭按钮的出现和消失。这将使 flex 正常工作。

然后,您无需跟踪鼠标移动,只需检查跨度上的点击即可。

示例 Fiddle(无 jQuery):http://jsfiddle.net/abhitalks/4fnvukc2/

示例代码段:

var closers = document.querySelectorAll('.closer'), 
    inputs = document.querySelectorAll('.closer input');

for (i=0; i < closers.length; i++) {
  closers[i].addEventListener('click', clearer);
}
for (i=0; i < inputs.length; i++) {
  inputs[i].addEventListener('input', closer);
}

function clearer(e) {
  if (e.target.tagName == 'SPAN') {
    e.target.firstElementChild.value = '';
    e.target.classList.remove('dirty');
  }
}
function closer(e) {
  e.stopPropagation();
  e.target.parentElement.classList.add('dirty');
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-size: 1em; font-family: sans-serif;}
div.field { 
  margin: 4em; 
  display: flex; justify-content: space-between;
}
span.closer {
  display: inline-block; position: relative;
  flex: 1 1 auto;
}
span.sep { flex: 0 1 auto; margin: 0 1em;}
span.closer::before {
  content: '×'; font-weight: bold;
  position: absolute; 
  right: -10px; top: 4px; opacity: 0;
  transition: all 0.2s ease-out;
}
span.closer.dirty::before { 
  right: 6px; opacity: 1; cursor: pointer;
}
span.closer.dirty:hover::before { color: #00f; }
.closer input { padding: 3px 16px 3px 4px; width: 100%; }
::-webkit-input-placeholder { color: #ccc; }
:-moz-placeholder { color: #ccc; }
<div class="field range">
  <span class="closer">
    <input type="text" id="start" placeholder="from" />
  </span>
  <span class="sep">–</span>
  <span class="closer">
    <input type="text" id="end" placeholder="until" />
  </span>    
</div>