如何创建自定义单选按钮?

J M*_*Mei 2 css sass radio-button reactjs

我一直在网上寻找创建自定义复选框和单选按钮的方法。我已设法创建一个复选框,但单选框遇到的一个问题是单击它不会激活或触发我输入的 onChange 调用。我目前有这个:

const customButtons = props => {
 const [isChecked, setChecked] = React.useState(false);

 const toggleCheck = e => {
  setChecked(e.target.checked || !isChecked)
 }

 return (
  <>
   <span className={container}>
    <input type={props.type} checked={isChecked} onChange={e => toggleCheck(e)} id={props.id} />
    <span></span>
   </span>
  </>
 ) 
}
Run Code Online (Sandbox Code Playgroud)

我已经使用 css 使跨度覆盖单选按钮并制作了原始单选按钮display: none;,但是当我单击跨度圆圈时,它不会触发单击。我向 span 添加了一个 onClick:<span onClick={toggleCheck}>但这会导致单击两次时单选按钮被取消选中。在保持原始行为的同时实现自定义单选按钮的更好方法是什么?

如果这很重要的话,我也在使用 scss。

iam*_*lli 5

您的方法适用于两者radiocheckboxes如果输入未设置为display: none,当然就像正常输入一样。但是,如果将它们设置为 display: none,则实际上是在 UI 事件中隐藏它们,因此它们根本不会触发任何点击。

\n\n
\n\n
\n

TLDR:更好的方法是,opacity: 0在输入上设置 ,使用标签来htmlFor触发更改。然后将标签伪元素设置为看起来像收音机。

\n
\n\n

这是实时代码沙箱的链接

\n\n
\n\n

由于您没有提供样式,因此很难判断您如何直观地布置自定义输入。用我的方法,

\n\n
    \n
  • 大多数 UIradios在只需要选择一个选项和checkboxes多项选择时使用。也就是说,可以轻松地将状态从各个无线电选项提升到父无线电组组件,然后传递无线电状态,同时让复选框控制其各自的状态,因为它们是相互独立构建的。

  • \n
  • 另一个观察结果是,您的收音机缺少name属性(Reason why you were seeing multiple clicks with just fewer or no change at all),导致它们彼此脱节。要将它们放在一个组中,它们需要共享一个共同的name属性,这样您只需针对每个无线电的选项值。

  • \n
  • 一旦选择了所有没有公共组(无名称属性)的单选选项,您就无法在 UI 上取消选择它们,因此它们不会触发任何进一步的 onChange 事件。因此,如果选项不是强制性的,建议添加重置选项以清除这些选项。

  • \n
\n\n

以下是每个无线电输入组件的代码。

\n\n
const RadioInput = ({ name, label, value, isChecked, handleChange }) => {\n  const handleRadioChange = e => {\n    const { id } = e.currentTarget;\n    handleChange(id); // Send back id to radio group for comparison\n  };\n\n  return (\n    <div>\n      {/* Target this input: opacity 0 */}\n      <input\n        type="radio"\n        className="custom-radio"\n        name={name}\n        id={value} // htlmlFor targets this id.\n        checked={isChecked}\n        onChange={handleRadioChange}\n      />\n      <label htmlFor={value}>\n        <span>{label}</span>\n      </label>\n    </div>\n  );\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n

label请注意,通常在编写自定义输入来覆盖本机输入时,如果您定位该元素并利用其forakahtmlFor属性来选择输入,则会更容易。从之前的努力来看,很难用自定义元素取悦所有屏幕阅读器,尤其是当您覆盖的本机input设置为不显示时。

\n\n

在我看来,最好将其绝对定位,将其不透明度设置为零,然后让标签触发其变化。

\n\n

此处链接到沙盒

\n\n
\n\n

组件的完整代码

\n\n

应用程序.js

\n\n
\n\nimport React, { useState } from "react";\nimport "./styles.scss";\n\n/*\nLet Checkbox the controls its own state.\nStyling \'custom-radio\', but only make the borders square in .scss file.\n*/\nconst CheckboxInput = ({ name, label }) => {\n  const [isChecked, setIsChecked] = useState(false);\n\n  const toggleCheck = e => {\n    setIsChecked(() => !isChecked);\n  };\n\n  return (\n    <div>\n      <input\n        type="checkbox"\n        className="custom-radio"\n        name={name}\n        id={name}\n        checked={isChecked}\n        onChange={toggleCheck}\n      />\n      <label htmlFor={name}>\n        <span>{label}</span>\n      </label>\n    </div>\n  );\n};\n\n/*\nThe custom radio input, uses the same styles like the checkbox, and relies on the \nradio group parent for its state.\n*/\nconst RadioInput = ({ name, label, value, isChecked, handleChange }) => {\n  const handleRadioChange = e => {\n    const { id } = e.currentTarget;\n    handleChange(id);\n  };\n\n  return (\n    <div>\n      <input\n        type="radio"\n        className="custom-radio"\n        name={name}\n        id={value}\n        checked={isChecked}\n        onChange={handleRadioChange}\n      />\n      <label htmlFor={value}>\n        <span>{label}</span>\n      </label>\n    </div>\n  );\n};\n\n/*\nThis is what control the radio options. Each radio input has the same name attribute\nthat way you can have multiple groups on the form.\n*/\nconst RadioGropupInput = () => {\n  const [selectedInput, setSelectedInput] = useState("");\n\n  const handleChange = inputValue => {\n    setSelectedInput(inputValue);\n  };\n\n  return (\n    <>\n      <div>\n        {/*\n          You could map these values instead from an array of options\n          And an option to clear the selections if they are not mandatory.\n          PS: Add aria attributes for accessibility\n        */}\n        <RadioInput\n          name="option"\n          value="option-1"\n          label="First Choice"\n          isChecked={selectedInput === "option-1"}\n          handleChange={handleChange}\n        />\n        <RadioInput\n          name="option"\n          value="option-2"\n          label="Second Choice"\n          isChecked={selectedInput === "option-2"}\n          handleChange={handleChange}\n        />\n        <RadioInput\n          name="option"\n          value="option-3"\n          label="Third Choice"\n          isChecked={selectedInput === "option-3"}\n          handleChange={handleChange}\n        />\n      </div>\n    </>\n  );\n};\n\nexport default () => (\n  <div className="App">\n    <RadioGropupInput />\n    <hr />\n    <CheckboxInput name="remember-me" label="Remember Me" />\n    <CheckboxInput name="subscribe" label="Subscribe" />\n  </div>\n);\n\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

风格

\n\n
\n\n.custom-radio {\n  /* Hide the input element and target the next label that comes after it in the DOM */\n  position: absolute;\n  display: inline-block;\n  opacity: 0;\n\n  & + label {\n    cursor: pointer;\n    display: inline-block;\n    position: relative;\n    white-space: nowrap;\n    line-height: 1rem;\n    margin: 0 0 1.5rem 0;\n    padding: 0 0 0 1rem;\n    transition: all 0.5s ease-in-out;\n\n    span {\n      margin-left: 0.5rem;\n    }\n\n    /* Styles these pseudo elements to look like radio inputs. */\n    &::before,\n    &::after {\n      content: \'\';\n      position: absolute;\n      color: #f5f5f5;\n      text-align: center;\n      border-radius: 0;\n      top: 0;\n      left: 0;\n      width: 1rem;\n      height: 1rem;\n      transition: all 0.5s ease-in-out;\n    }\n\n    &::before {\n      text-rendering: auto;\n      -webkit-font-smoothing: antialiased;\n      -moz-osx-font-smoothing: grayscale;\n      line-height: 1rem;\n      border-radius: 0;\n      background-color: #ffffff;\n      color: #ffffff;\n      box-shadow: inset 0 0 0 1px #666565, inset 0 0 0 1rem #ffffff,\n        inset 0 0 0 1rem #6b0707;\n    }\n\n    &:hover,\n    &:focus,\n    &:active {\n      color: red;\n      font-weight: bolder;\n      transition: all 0.3s ease;\n      outline: none;\n\n      &::before {\n        color: #ffffff;\n        animation-duration: 0.5s;\n        animation-name: changeSizeAnim;\n        animation-iteration-count: infinite;\n        animation-direction: alternate;\n        box-shadow: inset 0 0 0 1px #6b0707, inset 0 0 0 16px #ffffff,\n          inset 0 0 0 16px #6b0707;\n      }\n    }\n  }\n\n  &:focus,\n  &:hover,\n  &:checked {\n    & + label {\n      color: #220000 !important;\n    }\n\n    & + label::before {\n      animation-duration: 0.3s;\n      animation-name: selectCheckboxAnim;\n      animation-iteration-count: 1;\n      animation-direction: alternate;\n      border: solid 1px rgba(255, 0, 0, 0.5);\n      box-shadow: inset 0 0 0 1px #bc88d4, inset 0 0 0 0 #ffffff,\n        inset 0 0 1px 2px #6d1717;\n    }\n  }\n\n  &:checked {\n    & + label::before {\n      content: \'\xe2\x9c\x94\'; /* Swap out this emoji checkmark with maybe an icon font of base svg*/\n      background-color: #d43333;\n      color: #ffffff;\n      border: solid 1px rgba(202, 50, 230, 0.5);\n      box-shadow: inset 0 0 0 1px #bc88d4, inset 0 0 0 0 #ffffff,\n        inset 0 0 0 16px #d43333;\n    }\n  }\n\n  & + label {\n    &::before {\n      border-radius: 50%;\n    }\n  }\n\n  &[type=checkbox] {\n    & + label {\n      &::before {\n        /* Remove the border radius on the checkboxes for a square effect */\n        border-radius: 0;\n      }\n    }\n  }\n\n\n  @keyframes changeSizeAnim {\n    from {\n      box-shadow: 0 0 0 0 #d43333,\n        inset 0 0 0 1px #d43333,\n        inset 0 0 0 16px #FFFFFF,\n        inset 0 0 0 16px #d43333;\n    }\n\n    to {\n      box-shadow: 0 0 0 1px #d43333,\n        inset 0 0 0 1px #d43333,\n        inset 0 0 0 16px #FFFFFF,\n        inset 0 0 0 16px #d43333;\n    }\n  }\n\n  /* Add some animations like a boss, cause why would you hustle to build\n  a custom component when you can\'t touch this!\n  */\n  @keyframes selectCheckboxAnim {\n    0% {\n      box-shadow: 0 0 0 0 #bc88d4,\n        inset 0 0 0 2px #FFFFFF,\n        inset 0 0 0 3px #d43333,\n        inset 0 0 0 16px #FFFFFF,\n        inset 0 0 0 16px #d43333;\n    }\n\n    100% {\n      box-shadow: 0 0 20px 8px #eeddee,\n        inset 0 0 0 0 white,\n        inset 0 0 0 1px #bc88d4,\n        inset 0 0 0 0 #FFFFFF,\n        inset 0 0 0 16px #d43333;\n    }\n  }\n}\n\n/* Styles used to test out and reproduce out your approach */\n.container.control-experiment {\n  background: #fee;\n\n\n  span,\n  input {\n    display: flex;\n\n    border: solid 1px red;\n    width: 2rem;\n    height: 2rem;\n    line-height: 2rem;\n    display: inline-block;\n  }\n\n  input {    \n    position: absolute;\n    margin: 0;\n    padding: 0;\n  }\n\n  input[type=\'radio\'] {\n    // display: none; /* Uncommenting this out makes all your inputs unsable.*/\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

我重复一遍以强调,不要忘记添加自定义输入的 aria 属性。\n您可以再次测试实时沙箱

\n