仅使用CSS切换单选框输入

18

以下代码使用脚本在第二次点击相同的单选按钮时切换/取消选中状态。

我的问题是如何仅使用CSS实现此操作?

(function(lastimg) {
  document.querySelector("#img-select").addEventListener('click', function(e){
    if (e.target.tagName.toLowerCase() == 'input') {
      if (lastimg == e.target) {
        e.target.checked = false;
        lastimg = null;
      } else {
        lastimg = e.target;
      }      
    }
  });
}());
.container {
  display: flex;
  flex-wrap: wrap;
  max-width: 660px;
}
.container > label {
  flex: 1;
  flex-basis: 33.333%;
}
.container > div {
  flex: 1;
  flex-basis: 100%;
}
.container label img {
  display: block;
  margin: 0 auto;
}
.container input, .container input ~ div {
  display: none;
  padding: 10px;
}

.container #img1:checked ~ #img1txt,
.container #img2:checked ~ #img2txt,
.container #img3:checked ~ #img3txt,
.container #img4:checked ~ #img4txt {
  display: block;
}
<div id="img-select" class="container">
  <input id="img1" type="radio" name="img-descr">
  <input id="img2" type="radio" name="img-descr">
  <input id="img3" type="radio" name="img-descr">

  <label for="img1">
    <img src="http://lorempixel.com/200/200/food/1/" alt="">
  </label>
  <label for="img2">
    <img src="http://lorempixel.com/200/200/food/6/" alt="">
  </label>
  <label for="img3">
    <img src="http://lorempixel.com/200/200/food/8/" alt="">
  </label>

  <div id="img1txt">
    <div>Recipe nr 1</div>
  </div>
  <div id="img2txt">
    <div>Recipe nr 2</div>
  </div>
  <div id="img3txt">
    <div>Recipe nr 3</div>
  </div>
</div>

编辑

我需要一个跨浏览器的解决方案,能在主流浏览器上运行,并且无需脚本,只用CSS。

为了澄清,我希望它能够像普通单选框一样工作,但如果多次点击同一个单选框,则应像复选框一样切换自身状态。

此外,允许进行标记更改,只要布局结构保持相同,并且如果页面中有超过3个菜谱,我也希望它可以换行。

编辑2

问题的主要焦点是如何使单选框可切换,尽管一些答案展示了使用纯CSS切换状态的其他方法,任何这类技巧都是受欢迎的。


1
关于文本位置的问题,是全部放在左下角还是放在每张图片下面? - Stickers
1
@Pangloss 如何显示文本并不是最重要的问题,切换解决方案才是主要目标。 - Asons
1
好的,原因是标记可以因不同的文本显示而有很大的差异。 - Stickers
1
@Pangloss 可以做到,所以我更关心这里的逻辑,可以采取哪些技巧来实现可切换的单选框输入。 - Asons
1
你做错了什么。你所解释的用户体验是复选框用户体验。为什么不使用复选框,而坚持使用单选框呢? - cenk ebret
1
@cenkebret:我需要一个解决方案,其中输入应该同时作为单选框和复选框工作,只能选择/选中一个项目,如果单击相同的项目,则应切换选中/取消选中状态,...如果我选择复选框,那么这将无法正常工作。 - Asons
6个回答

18

使用CSS无法更改单选按钮的功能。CSS仅用于视觉变化。

话虽如此,您可以使用巧妙的技巧模拟此行为。针对您的示例,我建议使用CSS将当前选定单选按钮的标签在视觉上替换为附加到另一个单选按钮的虚假标签,该单选按钮表示“空”或“空白”选择。这样,单击虚拟标签将选择“空”选项,有效地清除了之前的选择:

.container {
  display: flex;
  flex-wrap: wrap;
  max-width: 660px;
}
.container > label {
  flex: 1;
  flex-basis: 33.333%;
}
.container > div {
  flex: 1;
  flex-basis: 100%;
}
.container label img {
  display: block;
  margin: 0 auto;
}
.container input, .container input ~ div {
  display: none;
  padding: 10px;
}

.container #img1:checked ~ #img1txt,
.container #img2:checked ~ #img2txt,
.container #img3:checked ~ #img3txt {
  display: block;
}

.container label[for=noimg] {
  display: none;
}

.container #img1:checked ~ label[for=img1],
.container #img2:checked ~ label[for=img2],
.container #img3:checked ~ label[for=img3] {
  display: none;
}

.container #img1:checked ~ label[for=img1] + label[for=noimg],
.container #img2:checked ~ label[for=img2] + label[for=noimg],
.container #img3:checked ~ label[for=img3] + label[for=noimg] {
  display: block;
}
<div id="img-select" class="container">
  <input id="noimg" type="radio" name="img-descr">
  <input id="img1" type="radio" name="img-descr">
  <input id="img2" type="radio" name="img-descr">
  <input id="img3" type="radio" name="img-descr">

  <label for="img1">
    <img src="http://lorempixel.com/200/200/food/1/" alt="">
  </label>
  <label for="noimg">
    <img src="http://lorempixel.com/200/200/food/1/" alt="">
  </label>
  <label for="img2">
    <img src="http://lorempixel.com/200/200/food/6/" alt="">
  </label>
  <label for="noimg">
    <img src="http://lorempixel.com/200/200/food/6/" alt="">
  </label>
  <label for="img3">
    <img src="http://lorempixel.com/200/200/food/8/" alt="">
  </label>
  <label for="noimg">
    <img src="http://lorempixel.com/200/200/food/8/" alt="">
  </label>

  <div id="img1txt">
    <div>Recipe nr 1</div>
  </div>
  <div id="img2txt">
    <div>Recipe nr 2</div>
  </div>
  <div id="img3txt">
    <div>Recipe nr 3</div>
  </div>
</div>

(在JSFiddle中查看)


1
谢谢,已接受。即使它有额外的图像(可以使用额外的包装器轻松删除),它可以正常工作,而不管图像是在正方形还是随机位置,这种情况经常发生。 - Asons

6
如果效果不需要持久存在,您可以通过使用:focus而不是使用单选按钮来实现类似的效果。
要使元素可聚焦,请将tabindex属性设置为整数。如果您不希望通过顺序聚焦导航(按“Tab”键)访问该元素,则使用负值。

.container {
  display: flex;
  flex-wrap: wrap;
  max-width: 660px;
}
.container > .img {
  flex: 1;
  position: relative;
}
.container > .img > .unselect {
  display: none;
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
}
.container > .txt {
  display: none;
  order: 1;
  flex-basis: 100%;
}
.container > .img:focus > .unselect,
.container > .img:focus + .txt {
  display: block;
}
<div id="img-select" class="container">
  <div class="img" tabindex="0">
    <img src="http://lorempixel.com/200/200/food/1/" alt="">
    <span class="unselect" tabindex="-1"></span>
  </div>
  <div class="txt">Recipe nr 1</div>
  <div class="img" tabindex="0">
    <img src="http://lorempixel.com/200/200/food/6/" alt="">
    <span class="unselect" tabindex="-1"></span>
  </div>
  <div class="txt">Recipe nr 2</div>
  <div class="img" tabindex="0">
    <img src="http://lorempixel.com/200/200/food/8/" alt="">
    <span class="unselect" tabindex="-1"></span>
  </div>
  <div class="txt">Recipe nr 3</div>
</div>


1
这个技巧很棒,我经常使用焦点,但不是为了这个目的,所以感谢您发布它,尽管在这种情况下我需要它持久化,所以不能选择它。 - Asons

5

接受赏金挑战(不带额外的noimg

.container {
    display: flex;
    flex-wrap: wrap;
    max-width: 660px;
    overflow: hidden;
    position: relative;
}
.container img {
    user-select: none;
    pointer-events: none;
}
.container > label {
    flex: 1;
    flex-basis: 33.333%;
    z-index: 1;
}
.container > div {
    flex: 1;
    flex-basis: 100%;
}
.container label img { margin: 0 auto }
.container input,
.container input ~ div {
    display: none;
    padding: 10px;
}
.container label[for=none] {
    position: absolute;
    bottom: 0;
    top: 0;
    left: 0;
    right: 0;
    z-index: 0;
}
.container #img1:checked ~ label[for=img1],
.container #img2:checked ~ label[for=img2],
.container #img3:checked ~ label[for=img3] {
    pointer-events: none;
    z-index: -1;
}
.container #img1:checked ~ #img1txt,
.container #img2:checked ~ #img2txt,
.container #img3:checked ~ #img3txt { display: block }
<div id="img-select" class="container">
  <input id="img1" type="radio" name="img-descr">
  <input id="img2" type="radio" name="img-descr">
  <input id="img3" type="radio" name="img-descr">

  <!-- Experimental -->
  <input id="none" type="radio" name="img-descr" checked>
  <label for="none"></label>

  <label for="img1">
    <img src="http://dummyimage.com/200/333" alt="">
  </label>
  <label for="img2">
    <img src="http://dummyimage.com/200/666" alt="">
  </label>
  <label for="img3">
    <img src="http://dummyimage.com/200/999" alt="">
  </label>

  <div id="img1txt">
    <div>Recipe nr 1</div>
  </div>
  <div id="img2txt">
    <div>Recipe nr 2</div>
  </div>
  <div id="img3txt">
    <div>Recipe nr 3</div>
  </div>
</div>


编辑:根据定义,单选按钮不应该是可切换的(我忘记这是此任务的附加要求=违规)。@Ajedi32的答案可能是最好的,但可以进行优化(重复的图片)?奖励仍在进行中...

编辑2:现在它是一个完全功能的解决方案。(使用这个技巧 https://dev59.com/pnA75IYBdhLWcg3wuLjD#7392038

编辑3:多层布局+修复选择。


1
谢谢,不过您可能误读了问题,它应该在重复单击同一图像时在选中/未选中之间切换,而您的代码没有实现这个功能。 - Asons
1
在我看来,使单选输入框可切换并不是破坏它,而是对其进行扩展。如果你问我,一个类型为“radiocheckbox”的输入框将非常有用。 - Asons
1
@LGSon,你对 EDIT 2 有什么看法? - Maciej A. Czyzewski
2
@Ajedi32,图像外可点击区域与标签比图像大有关,这不是什么大问题,很容易解决。我认为这个比你的更好 :) - Asons
2
感谢您的解决方案,您将获得奖励,尽管我会接受另一个解决方案,因为这个方案有一个缺陷,一旦可点击的图像不再位于正方形中,定位“none”标签就变得更加棘手(有时根本无法定位)。 - Asons
显示剩余7条评论

2
答案是你无法仅使用CSS取消选中或取消选中单选按钮,因为只有在单击其他单选按钮后,单选按钮才会变为未选中状态。由于一次只能激活一个单选按钮,这将取消先前选中的单选按钮。
input:checked + label {
    color: green;
}

input:not(:checked) + label {
    color: red;
}

所以你必须继续使用你发布的JS函数。

以下是一些很好的文章,进一步解释:

CSS点击事件

如何生成CSS点击事件


2
那篇后续文章提出了一些关于是否应该使用像这些答案中的CSS hack的好观点。这些方法的可维护性和模块化与仅使用JavaScript相比存在一些问题。绝对值得一读。 - Ajedi32

2
技巧在于使用:target。我在每个块中添加了两个空的<a>标签,并将它们设置为完全覆盖该块以执行点击事件。第一个链接用于真正的:target事件,而第二个链接只是为了撤消它,借助z-index的一点帮助来实现。 jsFiddle

ul {
  padding: 0;
  margin: 0;
}
li {
  display: inline-block;
  vertical-align: top;
  position: relative;
}
a {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
}
div {
  display: none;
}
.target{
  z-index: 1;
}
.target:target {
  z-index: -1;
}
.target:target ~ div{
  display: block;
}
<ul>
  <li>
    <a href="#link-1" id="link-1" class="target"></a>
    <a href="#"></a>
    <img src="//dummyimage.com/150/333">
    <div>a</div>
  </li>
  <li>
    <a href="#link-2" id="link-2" class="target"></a>
    <a href="#"></a>
    <img src="//dummyimage.com/150/666">
    <div>b</div>
  </li>
  <li>
    <a href="#link-3" id="link-3" class="target"></a>
    <a href="#"></a>
    <img src="//dummyimage.com/150/999">
    <div>c</div>
  </li>
</ul>


2
顺便提一下,你可能听说过 :target 可以使页面跳转,这里有一篇来自 CSS-Tricks 的优秀文章对此进行了讲解。 - Stickers
1
值得注意的是,这也会导致页面的URL更新,包括当前选定链接的哈希片段。(因此,在同一页上不能使用多个。)我想你可以通过将链接放在iframes中来解决这个问题... - Ajedi32
1
@Ajedi32 这是一个很好的观点,对我来说也是如此。在某些情况下,我已经使用 :target 来在页面加载时启用其他元素,这个方法非常完美。因此,我不能再将其用于处理状态... 否则,这是一个简单而有效的替代方案,适用于需要持久状态的单选输入。 - Asons

1

我有一个解决方案,已经实现了一半:

  • 它可以在双击时清除选择,但不能在单击时清除选择(按照要求)
  • 但是(这就是“一半的”部分),当双击当前选定的项目时,它将不会清除选择(只有在双击其他项目时才会)

 .container {
            display: flex;
            flex-wrap: wrap;
            max-width: 660px;
            overflow: hidden;
            position: relative;
        }

        .container img {
            user-select: none;
            pointer-events: none;
        }

        .container > label {
            flex: 1;
            flex-basis: 33.333%;
        }

        .container > div {
            flex: 1;
            flex-basis: 100%;
        }

        .container label img {
            margin: 0 auto
        }

        .container input,
        .container input ~ div {
            display: none;
            padding: 10px;
        }

        .container label[for=none] {
            position: absolute;
            width: 200px;
            height: 200px;
            z-index: 1;
            display: none;
        }

        .container #img1:checked ~ label[for=none],
        .container #img2:checked ~ label[for=none],
        .container #img3:checked ~ label[for=none] {
            display: block;
        }

        @keyframes hideNone {
            from {
                z-index: 3;
            }
            to {
                z-index: 3;
            }
        }

        .container #img1:checked ~ label[for=img1],
        .container #img2:checked ~ label[for=img2],
        .container #img3:checked ~ label[for=img3] {
            animation-name: hideNone;
            animation-delay: 0.3s;
            animation-duration: 99999s;
        }

        .container #img1:checked ~ label[for=none] {
            left: 0;
        }

        .container #img2:checked ~ label[for=none] {
            left: 220px;
        }

        .container #img3:checked ~ label[for=none] {
            left: 440px;
        }

        .container #img1:checked ~ #img1txt,
        .container #img2:checked ~ #img2txt,
        .container #img3:checked ~ #img3txt {
            display: block
        }
<div id="img-select" class="container">
    <input id="img1" type="radio" name="img-descr">
    <input id="img2" type="radio" name="img-descr">
    <input id="img3" type="radio" name="img-descr">

    <input id="none" type="radio" name="img-descr" checked>
    <label for="none"></label>

    <input type="button" autofocus>
    <label for="img1">
        <img src="http://dummyimage.com/200/333" alt="">
    </label>
    <label for="img2">
        <img src="http://dummyimage.com/200/666" alt="">
    </label>
    <label for="img3">
        <img src="http://dummyimage.com/200/999" alt="">
    </label>

    <div id="img1txt">
        <div>Recipe nr 1</div>
    </div>
    <div id="img2txt">
        <div>Recipe nr 2</div>
    </div>
    <div id="img3txt">
        <div>Recipe nr 3</div>
    </div>
</div>

仍在思考如何改进它... :)


1
谢谢,不过我需要完整的一个 :) - Asons

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接