Get input type=text to look like type=password

后端 未结 7 1876
南笙
南笙 2020-11-29 07:10

tl;dr

I have an input with type=text which I want to show stars like an input with type=password using only CSS.


Basically I

7条回答
  •  暖寄归人
    2020-11-29 08:01

    I ended up in this thread a lot of times recently. My solution is utilizing JQuery input event (though it can also be written in raw JS or even in C# (Blazor) should you need it, the idea would be the same):

    The core part is:

    if (isPasswordVisible) { // if password is visible, then simply update value stored in the dictionary
        value = newValue;
        passwordInputsValues[$passwordInput.attr("my-guid")] = value;
    } else { // else compute and update stored value
        const newValueUntilCaret = newValue.take(caretPosition); // take chars before the caret
        const unchangedCharsAtStart = newValueUntilCaret.takeWhile(c => c === "●").length; // count unchanged chars from the beginning
        const unchangedCharsAtEnd = newValue.skip(caretPosition).length; // count unchanged chars after the caret
        const insertedValue = newValueUntilCaret.skip(unchangedCharsAtStart); // get newly added string if any
        value = oldValue.take(unchangedCharsAtStart) + insertedValue + oldValue.takeLast(unchangedCharsAtEnd); // create new value as concatenation of old value left part, new string and old value right part
        passwordInputsValues[$passwordInput.attr("my-guid")] = value; // store newly created value in the dictionary
        $passwordInput.prop("value", value.split("").map(_ => "●").join("")); // set value of the input to new masked value
        $passwordInput[0].setSelectionRange(caretPosition, caretPosition); // set caret position to match the appropriate position 
    }
    

    Below is the complete code of an example password control (I will try to update it if any problems arise):

    // Utils 
    
    var guid = () => {
        return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
            const r = Math.random() * 16 | 0;
            const v = c === "x" ? r : r & 0x3 | 0x8;
            return v.toString(16);
        });
    }
    
    // Array Extensions
    
    Object.defineProperty(Array.prototype, "skip", {
        value: function (n) {
            if (typeof (n) !== "number") {
                throw new Error("n is not a number");
            }
            return this.slice(n);
        },
        writable: true,
        configurable: true
    });
    
    Object.defineProperty(Array.prototype, "take", {
        value: function (n) {
            if (typeof (n) !== "number") {
                throw new Error("n is not a number");
            }
            return this.slice(0, n);
        },
        writable: true,
        configurable: true
    });
    
    Object.defineProperty(Array.prototype, "takeLast", {
        value: function (n) {
            if (typeof (n) !== "number") {
                throw new Error("n is not a number");
            }
            return this.slice(Math.max(this.length - n, 0));
        },
        writable: true,
        configurable: true
    });
    
    Object.defineProperty(Array.prototype, "takeWhile", {
        value: function (condition) {
            if (typeof (condition) !== "function") {
                throw new Error("condition is not a function");
            }
    
            const arr = [];
            for (let el of this) {
                if (condition(el))
                    arr.push(el);
                else
                    break;
            }
            return arr;
        },
        writable: true,
        configurable: true
    });
    
    // String Extensions
    
    Object.defineProperty(String.prototype, "skip", {
        value: function (n) {
            return this.split("").skip(n).join("");
        },
        writable: true,
        configurable: true
    });
    
    Object.defineProperty(String.prototype, "take", {
        value: function (n) {
            return this.split("").take(n).join("");
        },
        writable: true,
        configurable: true
    });
    
    Object.defineProperty(String.prototype, "takeLast", {
        value: function (n) {
            return this.split("").takeLast(n).join("");
        },
        writable: true,
        configurable: true
    });
    
    Object.defineProperty(String.prototype, "takeWhile", {
        value: function (condition) {
            return this.split("").takeWhile(condition).join("");
        },
        writable: true,
        configurable: true
    });
    
    // JQuery Document Ready
    
    $(document).ready(function() {
        let isPasswordVisible = false;
        const passwordInputsValues = {};
    
        for (let $pi of $(".my-password-input").toArray().map(pi => $(pi))) {
            const uid = guid();
            $pi.attr("my-guid", uid);
            passwordInputsValues[uid] = $pi.prop("value");
        }
    	
        $(document).on("input", ".my-password-input", async function(e) {
            const $passwordInput = $(this);
            const newValue = $passwordInput.prop("value");
            const oldValue = passwordInputsValues[$passwordInput.attr("my-guid")] || ""; // first time it will be undefined
            const caretPosition = Math.max($passwordInput[0].selectionStart, $passwordInput[0].selectionEnd);
            let value;
    
            if (isPasswordVisible) {
                value = newValue;
                passwordInputsValues[$passwordInput.attr("my-guid")] = value;
            } else {
                const newValueUntilCaret = newValue.take(caretPosition);
                const unchangedCharsAtStart = newValueUntilCaret.takeWhile(c => c === "●").length;
                const unchangedCharsAtEnd = newValue.skip(caretPosition).length;
                const insertedValue = newValueUntilCaret.skip(unchangedCharsAtStart);
                value = oldValue.take(unchangedCharsAtStart) + insertedValue + oldValue.takeLast(unchangedCharsAtEnd);
                passwordInputsValues[$passwordInput.attr("my-guid")] = value;
                $passwordInput.prop("value", value.split("").map(_ => "●").join(""));
                $passwordInput[0].setSelectionRange(caretPosition, caretPosition);
            }
        });
    
        $(document).on("click", ".my-btn-toggle-password-visibility", function() {
            const $btnTogglePassword = $(this);
            const $iconPasswordShown = $btnTogglePassword.find(".my-icon-password-shown");
            const $iconPasswordHidden = $btnTogglePassword.find(".my-icon-password-hidden");
            const $passwordInput = $btnTogglePassword.parents(".my-input-group").first().children(".my-password-input").first();
            const value = passwordInputsValues[$passwordInput.attr("my-guid")];
    
            if (!isPasswordVisible) {
                $iconPasswordHidden.removeClass("my-d-flex").addClass("my-d-none");
                $iconPasswordShown.removeClass("my-d-none").addClass("my-d-flex");
                $passwordInput.prop("value", value);
                isPasswordVisible = true;
            } else {
                $iconPasswordShown.removeClass("my-d-flex").addClass("my-d-none");
                $iconPasswordHidden.removeClass("my-d-none").addClass("my-d-flex");
                $passwordInput.prop("value", value.split("").map(_ => "●").join(""));
                isPasswordVisible = false;
            }
        });
    });
    body {
        padding-top: 0;
        color: white;
        margin: 0;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
        font-size: 16px;
        font-weight: 400;
        line-height: 1.5;
        text-align: left;
        height: 100%;
        max-height: 100%;
        background-image: linear-gradient(rgba(0,0,0,0.2), rgba(0,0,0,0.2)), url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAABGCAAAAABURb1YAAAFnklEQVR4AWWXCY4cwXbE6h4JMhCvMPc/ov+3Ox9QsLCGpKlFHILd+UQ559AGDkBSFXWmomonqkL/XuUcIPUcONjHCJyE36KaqNiaRnVCG9TGpgIU9hIfohCBVGj24ammo04lU3Fy0NZDAyZi5AElHjgEGlRbG1WjraIt7TmADQkAaoEHsOXAAaeqat9RVTqjosyEH5W+DQCQt8BzIJMfXJI3e5tGRefSlRn3NrO3scnvNl26FN5RITGNfjm3GM/+FhQgnlQeImC+dEm8rzHVTNGL9EADVsTAIfGJBziRy7RlmcZU1IYpHIj+4JrWym8/XSwzompmFNVOFdTOAAfs61KZ6eXcp57fykQUOxNBmTesz2HpCnCg3UV/bA7VRpWGNqKtrYLTs0ilCjgxiZxjeLC5dDPBFn4Pn2ja6BRUL10DTgET4Focq8rn8casxV4E5tIlZvytqtfivCOKZrqc38G7egnkXecyPZdzubc57VtUnbfL+a0u3b1N3d9Mci7cKocHoh3xihLQ6fXUlgTOOfVkXSUFINoWHhqxE23RTmCqtoEJaITkIrUCkU9fnqpqm+40VSXS3CLaSzfGBb4rPuOSfN0ylLve6/jpcrbL2dTlnOdv5AamKjLvLN1XruMNS/cul/hx8twKpKYRaE2DOp8+c+lW7uVXVQ7Vx0x1G3HXjNocsHCac7NbWc6egwJVHu2kRTVNC2pqv2U4XNsv5/yWJlV9+NJ1C7xluInlcr7rK+Kg+qy7onQqgnbi7UHcS3qXjdw3bO5tumycYlVssMI5EXXpRnatPK362KodtVU60cm+eQT10sVLFwJYlVaf7UGSoi5dQF3DvnTX3UvXTPO8quKH7jsXhm/PxfKWZXqBM5sS33kmKtC31Q05fI095i5IPpyzRXqSy1Sr17p1F7NE8vMtnNbLpVVtk8c0OrfF0sKxHnIpYgHW4puMomTz8mgmrSjpvrxhX97l7LqLpl7bW9BH9/MNzF8XwRqL8/m7c30eFYV5VX3QTquK06XbLt16F4ks+8Z9m6gPtpcuk3OvibCekng4lzMHi9MoqZmKj9tdbQCbc7bcEA+oEJduRNNL1waf1tuI9MDBUK8t3k9uFz3rrqadO9Ln9TL/ezlwDvky3TW7ZmXfpc77TN0yhOXnP939LvVDV9s+tgLTm0TqYd1lea3PRFuVjLa562HdPbCfascGrvO3DMeIATVqVRO8vfHrbupdfo1dzlXUdkVLZsS1mLPG3rWcl+4B1l1537h0VXzU1tyLG7cCu+LSrSqo06hocy3WCcil65qHDbuEYzQN2HpT3jFt9LEFvHTP1nxbXOEkbhkKmd+6BTfVZ/I143z/Fyz6Za92o0I7I6gzfeYAHPwy/Rb43L9TLtMt8CtwW1xvQvbR1g9n7qJRNN1kdxcdH7cM58Szxq679MdLOhE72t6VNEpHHkzwRyjBcGth+dE99pIkVbWaEWmvnQ8Yl7Mp59zpRWXdNo6odrO754Zzvxf/010nP7onb1TR+bgbVPB9PcADJvH/043Kvk0UzLRxQ6UiaeTAcyxYl2lyrrvZv8v2wE7U1uVcST0821jJeIDKWuyxiqnuko7q3+zJYXJ46nV3D67EyL+lQ9F2qkr+3kUafQpwYE8T329p6y7OX1TF97fQv/c+l87zj7Hsgpig4qRLd9fMPs74rLGyCzRbhmVqpy7nqvPmTD1gvKe7b2U8u1QzmtuDuEtbgT3c8QBJveo6cueFkUwv59nVmfFw1jl4/unuOT9qERV76ers8n23z/c8/Xy7a4lwwLplmGusbRp33Up13+ZDN3KPxNVMhY62vUQyRTse6+cKHtZYjIfDh260Cjbb3Vy6R8H8SMBzqSwrsB1UTacoknlnOb8V4JD9Njfpwz/fHjpBUeevcAtcl65sqXPX9D87RUTLc/dfOgAAAABJRU5ErkJggg==);
        background-clip: border-box;
        background-origin: padding-box;
        background-attachment: scroll;
        background-repeat: repeat;
        background-size: auto;
        background-position: left top;
    }
    
    .snippet-container {
        background: linear-gradient(135deg, #202020, black);
        position: relative;
        display: flex;
        justify-content: center;
        align-items: center;
        width: 100%;
        height: 200px;
    }
    
    .my-password-input {
        background: linear-gradient(to bottom, #303030, #000000);
        color: white;
        display: block;
        position: relative;
        box-sizing: border-box;
        padding: 5px 9px;
        line-height: 24px;
        height: 34px;
        box-shadow: inset 0 0 0 1px #404040;
        font-size: 16px;
        font-weight: 400;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
        transition: all .15s ease-in-out;
        width: 100%;
        border: none;
    }
    
    .my-password-input:enabled:focus {
        color: white;
        box-shadow: inset 0 0 0 1px #404040, 0 0 6px 2px blue;
        outline: none;
    }
    
    .my-input-group {
        position: relative;
    }
    
        .my-input-group > .my-input-group-prepend {
            display: flex;
            position: absolute;
            left: 0;
            top: 0;
        }
    
        .my-input-group > .my-input-group-append {
            display: flex;
            position: absolute;
            right: 0;
            top: 0;
        }
    
    .my-icon {
        display: flex;
        align-items: center;
        justify-content: center;
        overflow: hidden;
    }
    
    .my-input-group > .my-input-group-prepend > .my-icon,
    .my-input-group > .my-input-group-append > .my-icon {
        width: auto;
        height: 16px;
        max-width: none;
        max-height: 16px;
        flex: 0 0 auto;
        margin: 9px;
    }
    
        .my-input-group > .my-input-group-prepend > .my-icon > svg,
        .my-input-group > .my-input-group-append > .my-icon > svg {
            height: 100%;
            width: auto;
            margin: 0;
            padding: 0;
            overflow: hidden;
        }
    
    .my-input-group > .my-input-group-prepend > .my-btn,
    .my-input-group > .my-input-group-append > .my-btn {
        height: 100% !important;
        width: auto;
    }
    
    button:enabled {
        cursor: pointer;
    }
    
    .my-btn {
        background: linear-gradient(to bottom, #303030, #000000);
        color: white;
        position: relative;
        box-sizing: border-box;
        padding: 5px;
        line-height: 24px;
        height: 34px;
        font-size: 16px;
        font-weight: 400;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
        transition: all .15s ease-in-out;
        width: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
        border: none;
        box-shadow: 0 0 0 0 #FFFFFF, inset 0 0 0 1px #404040;
    }
    
    .my-btn-primary {
        color: #fff;
        background: linear-gradient(to bottom, #00008B, #000000);
        box-shadow: 0 0 0 0 #FFFFFF, inset 0 0 0 1px #0000FF;
    }
    
        .my-btn-primary:hover:enabled {
            box-shadow: 0 0 6px 2px #FFFFFF, inset 0 0 0 1px #FFFFFF;
            background: linear-gradient(to top, #00008B, #000000);
        }
    
    .my-btn > .my-icon {
        margin: 4px;
        width: auto;
        height: 16px;
        max-width: none;
        max-height: 16px;
        flex: 0 0 auto;
    }
    
        .my-btn > .my-icon > svg {
            height: 100%;
            width: auto;
        }
    
    .my-d-none {
        display: none !important;
    }
    
    .my-d-flex {
        display: flex !important;
    }
    
    ::-webkit-input-placeholder {
        color: #404040;
        font-style: italic;
    }
    
    :-moz-placeholder {
        color: #404040;
        font-style: italic;
    }
    
    ::-moz-placeholder {
        color: #404040;
        font-style: italic;
    }
    
    :-ms-input-placeholder {
        color: #404040;
        font-style: italic;
    }
    
    ::-moz-selection {
        background-color: #f8b700;
        color: #352011;
    }
    
    ::selection {
        background-color: #f8b700;
        color: #352011;
    }
    
    
    

提交回复
热议问题