Accordion component keyboard commands - javascript
I have an accordion component which is working correctly using the 'tab' to navigate through the controls, and on 'enter' and 'spacebar' the accordion expands. I am trying to figure out a way to navigate to the next accordion using the 'up' and 'down' arrow keys. I am familiar with Javascript but I have not been able to achieve this with my existing code. Any help I can get is greatly appreciated.
Here's a CodePen to my accordion component. https://codepen.io/ozman2182/pen/vYgvGOd
(function () {
const headings = document.querySelectorAll(".unr-accordion--heading");
Array.prototype.forEach.call(headings, (h) => {
let btn = h.querySelector("button");
let target = h.nextElementSibling;
btn.onclick = () => {
let expanded = btn.getAttribute("aria-expanded") === "true";
btn.setAttribute("aria-expanded", !expanded);
target.hidden = expanded;
};
});
})();
In the example at the end of this answer I have added the code required to make the arrow keys cycle up and down through the list (and loop around).
I have also added Home and End keys to go to the start and end of the list (as that is expected behaviour).
In summary we:
grab all the buttons with .querySelectorAll('.unr-accordion--heading>button');
add an event listener for "keydown"
see if the keyCode is 38 (up) or 40 (down), if it is we apply a "direction" of -1 (to go up one item in our list of buttons) or +1 (to go down).
if the keyCode is 36 ("Home") - we set a direction of -999 so we can check it later
if the keyCode is 35 ("End") - we set a direction of 999 so we can check it later
if a direction is set (up or down arrow was pressed or home / end) we then loop through all the buttons.
if the button in the current stage of the loop equals document.activeElement (the currently focused item) then we know we are both in the accordion and on a button and the arrow keys should function.
We then check if the direction is up and we are focused on the first item in the buttons list (direction == -1 && x == 0) or if the "direction" is -999 (the home key), so we can loop to the bottom of the list of buttons and focus that one. We exit the loop if so with break;
If not we then check if the direction is down and we are focused on the last item in the buttons list (direction == 1 && x == max) or if the "direction" is +999 (the end key), so we can loop to the top of the list of buttons and focus that one. We exit the loop if so with break;
finally if neither of the above are true we just move focus by the direction (-1 for up, +1 for down) and then exit the loop.
(function () {
const headings = document.querySelectorAll(".unr-accordion--heading");
Array.prototype.forEach.call(headings, (h) => {
let btn = h.querySelector("button");
let target = h.nextElementSibling;
btn.onclick = () => {
let expanded = btn.getAttribute("aria-expanded") === "true";
btn.setAttribute("aria-expanded", !expanded);
target.hidden = expanded;
};
});
var btns = document.querySelectorAll('.unr-accordion--heading>button');
document.addEventListener('keydown', function(e){
var direction = 0;
var max = btns.length - 1;
direction = (e.keyCode == 38) ? -1 : direction;
direction = (e.keyCode == 40) ? 1 : direction;
direction = (e.keyCode == 35) ? -999 : direction;
direction = (e.keyCode == 36) ? 999 : direction;
if(direction != ""){
e.preventDefault();
for(x = 0; x <= max; x++){
if(document.activeElement == btns[x]){
if(direction == -1 && x == 0 || direction == -999){
btns[max].focus();
break;
}
if(direction == 1 && x == max || direction == 999){
btns[0].focus();
break;
}
btns[x + direction].focus();
break;
}
}
}
})
})();
:root {
--blue-10: #E6E9EC;
--blue-20: #CDD2D9;
--blue-50: #828FA1;
--blue-80: #364B68;
--blue-100: #041E42;
}
html {
font-family: Helvetica, sans-serif;
color: var(--blue-100);
}
section {
max-width: 920px;
margin-top: 3em;
margin-right: auto;
margin-left: auto;
}
.unr-accordion--expandall {
margin-bottom: 1em;
border:2px solid var(--blue-20);
border-radius: 5px;
padding: 0.5em 1em;
background-color: white;
}
.unr-accordion--expandall:hover,
.unr-accordion--expandall:focus {
border:2px solid var(--blue-10);
background-color: var(--blue-10);
}
.unr-accordion--wrapper {
border: 2px solid var(--blue-20);
border-radius: 5px;
margin-bottom: 0.5em;
}
.unr-accordion--wrapper:last-child {
margin-bottom: 0;
}
.unr-accordion--wrapper > h2 {
display: flex;
margin: 0;
border-radius: 5px;
}
.unr-accordion--wrapper > h2 button {
all: inherit;
border: 0;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
margin: 0;
padding: 0.5em;
font-size: 1.5rem;
line-height: 1.5;
}
.unr-accordion--wrapper > h2 button:hover {
background-color: var(--blue-10);
}
.unr-accordion--wrapper > h2 button svg {
font-size: 1rem;
margin-left: 0.5em;
flex-shrink: 0;
}
.unr-accordion--wrapper > h2 button:focus svg {
outline: 2px solid;
}
.unr-accordion--wrapper > h2 button[aria-expanded="true"] {
background-color: var(--blue-10);
}
.unr-accordion--wrapper > h2 button[aria-expanded="true"] .vert {
display: none;
}
.unr-accordion--wrapper > h2 button[aria-expanded] rect {
fill: currentColor;
}
.unr-accordion--panel {
margin-top: -1em;
padding-top: 1em;
padding-right: 1em;
padding-bottom: 1em;
padding-left: 1em;
background-color: var(--blue-10);
}
<section>
<h1>Edgar Allan Poe was an American writer, poet, editor, and literary critic.</h1>
<p>Poe is best known for his poetry and short stories, particularly his tales of mystery and the macabre. He is widely regarded as a central figure of Romanticism in the United States and of American literature as a whole, and he was one of the country's earliest practitioners of the short story.</p>
<!-- <button class="unr-accordion--expandall" href="#">Expand All</button> -->
<!-- accordion items -->
<div class="unr-accordions">
<div class="unr-accordion--wrapper">
<h2 class="unr-accordion--heading">
<button aria-expanded="false">
The Black Cat (short story)
<svg viewbox="0 0 10 10" width="24px" height="24px" aria-hidden="true" focusable="false">
<rect class="vert" height="8" width="2" y="1" x="4" />
<rect height="2" width="8" y="4" x="1" />
</svg>
</button>
</h2>
<div class="unr-accordion--panel" hidden>
<p>It was first published in the August 19, 1843, edition of The Saturday Evening Post. In the story, an unnamed narrator has a strong affection for pets until he perversely turns to abusing them.</p>
</div>
</div>
<div class="unr-accordion--wrapper">
<h2 class="unr-accordion--heading">
<button aria-expanded="false">
The Cask of Amontillado
<svg viewbox="0 0 10 10" width="24px" height="24px" aria-hidden="true" focusable="false">
<rect class="vert" height="8" width="2" y="1" x="4" />
<rect height="2" width="8" y="4" x="1" />
</svg>
</button>
</h2>
<div class="unr-accordion--panel" hidden>
<p>First published in the November 1846 issue of Godey's Lady's Book. The story, set in an unnamed Italian city at carnival time in an unspecified year, is about a man taking fatal revenge on a friend who, he believes, has insulted him. Like several of Poe's stories, and in keeping with the 19th-century fascination with the subject, the narrative revolves around a person being buried alive – in this case, by immurement. As in "The Black Cat" and "The Tell-Tale Heart", Poe conveys the story from the murderer's perspective.</p>
</div>
</div>
<div class="unr-accordion--wrapper">
<h2 class="unr-accordion--heading">
<button aria-expanded="false">
The Gold-Bug
<svg viewbox="0 0 10 10" width="24px" height="24px" aria-hidden="true" focusable="false">
<rect class="vert" height="8" width="2" y="1" x="4" />
<rect height="2" width="8" y="4" x="1" />
</svg>
</button>
</h2>
<div class="unr-accordion--panel" hidden>
<p>The plot follows William Legrand, who was bitten by a gold-colored bug. His servant Jupiter fears that Legrand is going insane and goes to Legrand's friend, an unnamed narrator, who agrees to visit his old friend. Legrand pulls the other two into an adventure after deciphering a secret message that will lead to a buried treasure. </p>
</div>
</div>
</div>
</div>
<!-- end: accordion component -->
</section>
Related
How could I get each field in the circle to be the same size and clickable? [duplicate]
This question already has answers here: CSS Only Pie Chart - How to add spacing/padding between slices? (2 answers) Closed 5 months ago. For the following code: REPL How could I make each field of the circle be the same size in a simple way? The fields on the top and bottom are currently bigger than the other ones for obvious reasons. I could do some complicated calculations with position relative and absolute, but I wonder if there's an easy solution for my problem? This is what it should look like:
Here's a variable <svg> solution REPL Unfortunately not every field is seperately clickable which I just saw you mentioned in a comment. That's a crucial fact which you might add to your question. Edit: By changing to fill: none; actually every field is seperately clickable, thanks #herrstrietzel <script> const size = 300 let strokeWidth = 25 let gap = 20 $: circumference = Math.PI * (size - strokeWidth) $: r = size/2 - strokeWidth/2 let pieces = [ {stroke: 'teal'}, {stroke: 'magenta'}, {stroke: 'orange'}, ] let color = '#1411DF' </script> <div style:width="{size}px"> <svg width={size} height={size} style="transform: rotate({-90+(gap/2/r/Math.PI*180)}deg)"> {#each pieces as piece, index} {#const ownLength = circumference / pieces.length - gap} <circle r={r} cx={size/2} cy={size/2} style:stroke-width={strokeWidth} style:stroke={piece.stroke} style:stroke-dasharray="{ownLength} {circumference}" style="fill: none; transform-origin: center;" style:transform="rotate({index * 360 / pieces.length}deg)" on:click="{() => console.log(piece.stroke)}" /> {/each} </svg> <input type="color" bind:value={color}> <button on:click={() => pieces = [...pieces, {stroke: color}]}> add piece </button> <label> stroke-width: <input type="range" bind:value={strokeWidth} min="1" max={size/5}> {strokeWidth} </label> <label> gap: <input type="range" bind:value={gap} min="1" max={size/5}> {gap} </label> </div> <style> circle:hover { stroke: black !important; } div { margin: 0 auto; display: flex; flex-direction: column; align-items: center; } svg { display: block; margin-bottom: 2rem; } label { width: 100%; font-size: .9rem; padding: 1rem 0; } input { padding: 0; } </style> And inspired by H.B.'s answer a different solution with path elements REPL <script> let colors = ['teal', 'DarkOrchid', 'orange'] let color = '#2E15D1' const size = 300 let strokeWidth = 10 let gap = 10 // degree $: r = size/2 - strokeWidth/2 $: deg = (180 - (360 / colors.length) + gap) / 2 $: x = r * Math.cos(rad(deg)) $: y = r * Math.sin(rad(deg)) function rad(angle) { return angle * Math.PI / 180; } </script> <div style:width="{size}px"> <svg width={size} height={size} viewbox="{-size/2} {-size/2} {size} {size}" xmlns="http://www.w3.org/2000/svg"> {#each colors as color, index} <path d="M -{x} -{y} A {r} {r} 0 0 1 {x} -{y}" style="fill: none;" style:stroke={color} style:stroke-width="{strokeWidth}" style:transform="rotate({360 / colors.length * index}deg)" on:click="{() => console.log(color)}" /> {/each} <!-- <circle cx="0" cy="0" {r} fill="none" stroke="black"></circle> --> <!-- <circle cx="0" cy="0" r="2"></circle> --> </svg> <input type="color" bind:value={color}> <button on:click={() => colors = [...colors, color]}> add piece </button> <label> stroke-width: <input type="range" bind:value={strokeWidth} min="1" max={size/5}> {strokeWidth} </label> <label> gap: <input type="range" bind:value={gap} min="1" max={(360/colors.length)-1}> {gap}° </label> </div> <style> div { margin: 0 auto; display: flex; flex-direction: column; align-items: center; } svg { margin: 2rem; /* transform: rotate(180deg); */ } path:hover { stroke: black !important; } input { padding: 0; margin: .4rem; } label { display: grid; align-items: center; grid-template-columns: 1fr max-content 1fr; font-size: .9rem; white-space: nowrap; } </style>
I could do some complicated calculations with position relative and absolute, but I wonder if there's an easy solution for my problem? I'm afraid there is no solution completely without calculations, but at least this one is not complicated. You can use conic-gradient and split it by degrees (in my example by 60deg) but the corners tend to be too pixelated. You can also use repeating-conic-gradient. div { position: relative; width: 200px; height: 200px; border-radius: 100%; background: conic-gradient( /* per 60deg - 5*2deg for white space */ white 5deg, red 5deg 55deg, white 55deg 65deg, orange 65deg 115deg, white 115deg 125deg, blue 125deg 175deg, white 175deg 185deg, pink 185deg 235deg, white 235deg 245deg, gray 245deg 295deg, white 295deg 305deg, yellow 305deg 355deg, white 355deg 360deg ); } div:after { content: ''; position: absolute; left: 20px; top: 20px; width: 160px; height: 160px; background: white; border-radius: 100%; } <div></div>
You could use an SVG. You can create path elements with arcs and stroke them in the respective color. Arcs are fairly complicated with many parameters: A rx ry x-axis-rotation large-arc-flag sweep-flag x y a rx ry x-axis-rotation large-arc-flag sweep-flag dx dy I would recommend reading the MDN documentation or the spec. Example of a segment: <svg width="320" height="320" xmlns="http://www.w3.org/2000/svg"> <path d="M 50 50 a 50 50 0 0 1 50 0" stroke="black" stroke-width="20" fill="none"/> </svg> The easiest method is probably calculating the angles (start/end) and converting from polar to cartesian. The paths only need two commands: Absolute move to start location & absolute arc to end location. Full example with clickable segments and keyboard support for additional accessibility (could still be improved, e.g. by supplying screen reader texts): <script> let radius = 150; let stroke = 20; let gap = 5; let segments = [ '#ff0000', '#00ff00', '#0000ff', ]; function getCoordinates(i, gap) { const angleDelta = 360 / segments.length; const start = polarToCartesian(radius, i * angleDelta + gap); const end = polarToCartesian(radius, i * angleDelta + angleDelta); return { start, end }; } const polarToCartesian = (r, angle) => { return { x: r * Math.cos(rad(angle)), y: r * Math.sin(rad(angle)), } } const rad = x => x * Math.PI / 180; const onClick = i => alert('Segment ' + i); </script> <div> <svg width="320" height="320" xmlns="http://www.w3.org/2000/svg"> <g transform="translate(160, 160) rotate({-90 - gap/2})"> {#each segments as segment, i (i)} {#const { start, end } = getCoordinates(i, gap)} <path d="M {start.x} {start.y} A {radius} {radius} 0 0 1 {end.x} {end.y}" stroke={segment} stroke-width={stroke} fill="none" tabindex={0} on:keydown={e => { if (e.key == 'Enter') onClick(i); }} on:click={() => onClick(i)} /> {/each} </g> </svg> </div> REPL
Align first and last item in a glidejs individually
I am using glidejs for some slideshows on a webpage and I love it. Now I want to slide some content. The first element should be aligned left, the last aligned right and all other elements aligned centered. I got some good results with focusAt for the first element, but it doenst seem to work for the last element. var glide = new Glide('#glide', { type: 'slider', focusAt: 'center', perView: 1.5, focusAt: 0 }) glide.on("run", () => { var focusAt = "center"; if (glide.index === 0) focusAt = 0; glide.update({ focusAt: focusAt }); }); glide.mount(); #glide { text-align: center; } .glide__track { border: 2px solid #000; width: 300px; margin: 10px auto; } .glide__slide { height: 150px; text-align: center; line-height: 150px; border: 2px solid #000; background-color: #ccc; font-size: 64px; font-weight: bold; font-family: arial; color: #999; } .glide__bullet { width: 10px; height: 10px; display: inline-block; padding: 0; } .glide__bullet--active { background-color: #000; } <link href="https://cdnjs.cloudflare.com/ajax/libs/Glide.js/3.2.0/css/glide.core.min.css" rel="stylesheet"/> <script src="https://cdnjs.cloudflare.com/ajax/libs/Glide.js/3.5.0/glide.min.js"></script> <div id="glide" class="glide glide--ltr glide--slider glide--swipeable"> <div class="glide__track" data-glide-el="track"> <ul class="glide__slides"> <li class="glide__slide">0</li> <li class="glide__slide">1</li> <li class="glide__slide">2</li> <li class="glide__slide">3</li> <li class="glide__slide">4</li> </ul> </div> <div data-glide-el="controls"> <button class="slider__arrow slider__arrow--prev glide__arrow glide__arrow--prev" data-glide-dir="<"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"> <path d="M0 12l10.975 11 2.848-2.828-6.176-6.176H24v-3.992H7.646l6.176-6.176L10.975 1 0 12z"></path> </svg> </button> <button class="slider__arrow slider__arrow--next glide__arrow glide__arrow--next" data-glide-dir=">"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"> <path d="M13.025 1l-2.847 2.828 6.176 6.176h-16.354v3.992h16.354l-6.176 6.176 2.847 2.828 10.975-11z"></path> </svg> </button> </div> <div class="slider__bullets glide__bullets" data-glide-el="controls[nav]"> <button class="glide__bullet" data-glide-dir="=0"></button> <button class="glide__bullet" data-glide-dir="=1"></button> <button class="glide__bullet" data-glide-dir="=2"></button> <button class="glide__bullet" data-glide-dir="=3"></button> <button class="glide__bullet" data-glide-dir="=4"></button> </div> </div> Any ideas?
Solved the problem with a mutator. const glide = new Glide(container, { type: "slider", perView: 1.25, focusAt: 0, gap: 20, }); const mutator = function (Glide, Components, Events) { return { modify(translate) { // First slide if (Glide.index === 0) { // Move slide 20 pixels from left return translate - 20; // Last slide } else if (Glide.index === Components.Sizes.length - 1) { // Move slide 20 pixels from right return translate - (Components.Sizes.width - Components.Sizes.slideWidth) + 20; // Other slides } else { // Center slide return translate - (Components.Sizes.width - Components.Sizes.slideWidth) / 2; } }, }; }; glide.mutate([mutator]).mount();
Dynamically added svg viewbox values not working? [duplicate]
This question already has an answer here: Cannot scale svg if created with js (1 answer) Closed 10 months ago. I'm trying to add an svg dynamically to a menu, but I'm having a problem setting the viewbox. When I inline the svgs (svg and svg-2), the viewbox has to be correct for the respective icon (0 0 512 512 and 0 0 24 24), as expected. Changing the viewbox removes the icon from view. But for my dynamically added icons (dynamic-svg and dynamic-svg-2), changing the viewbox values (0 0 24 24 and 0 0 512 512) does nothing. In fact I can't get dynamic-svg-2 to show at all. dynamic-svg continues to display even if I change the viewbox to random values. I must be doing something wrong, or have a bug somewhere, but I really can't see it. Would appreciate if someone could take a look. Thanks codepen const container = document.querySelector('.container'); const svgWrapper = document.createElement('div'); svgWrapper.className = 'dynamic-svg-wrapper' const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg") const path1 = document.createElementNS("http://www.w3.org/2000/svg", 'path') svg.setAttribute("aria-hidden","true"); svg.setAttribute('viewbox', '0 0 24 24'); svg.setAttribute('class', 'dynamic-svg'); path1.setAttribute('d', `M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z`); path1.setAttribute('fill', '#000000'); svg.append(path1); svgWrapper.append(svg); container.append(svgWrapper); const svgWrapper2 = document.createElement('div'); svgWrapper2.className = 'dynamic-svg-wrapper-2' const svg2 = document.createElementNS("http://www.w3.org/2000/svg", "svg") const path2 = document.createElementNS("http://www.w3.org/2000/svg", 'path') svg2.setAttribute("aria-hidden","true"); svg2.setAttribute('viewbox', '0 0 512 512'); svg2.setAttribute('class', 'dynamic-svg-2'); path2.setAttribute('d', `M461.6,109.6l-54.9-43.3c-1.7-1.4-3.8-2.4-6.2-2.4c-2.4,0-4.6,1-6.3,2.5L194.5,323c0,0-78.5-75.5-80.7-77.7 c-2.2-2.2-5.1-5.9-9.5-5.9c-4.4,0-6.4,3.1-8.7,5.4c-1.7,1.8-29.7,31.2-43.5,45.8c-0.8,0.9-1.3,1.4-2,2.1c-1.2,1.7-2,3.6-2,5.7 c0,2.2,0.8,4,2,5.7l2.8,2.6c0,0,139.3,133.8,141.6,136.1c2.3,2.3,5.1,5.2,9.2,5.2c4,0,7.3-4.3,9.2-6.2L462,121.8 c1.2-1.7,2-3.6,2-5.8C464,113.5,463,111.4,461.6,109.6z`); path2.setAttribute('fill', '#000000'); svg2.append(path2); svgWrapper2.append(svg2); container.append(svgWrapper2); * { margin: 0; } .container { display: flex; justify-content: center; width: 100%; height: 100vh; background-color: lightgrey; } .svg-wrapper { display: flex; width: 24px; height: 24px; border: 1px solid black; margin: 10px; } .svg { display: flex; width: 24px; height: 24px; } .svg-wrapper-2 { display: flex; width: 24px; height: 24px; border: 1px solid black; margin: 10px; } .dynamic-svg-wrapper { display: flex; width: 24px; height: 24px; border: 1px solid black; margin: 10px; } .dynamic-svg { display: flex; width: 24px; height: 24px; } .dynamic-svg-wrapper-2 { display: flex; width: 24px; height: 24px; border: 1px solid black; margin: 10px; } .dynamic-svg-2 { display: flex; width: 24px; height: 24px; } <div class="container"> <div class="svg-wrapper"> <svg class="svg" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 512 512"> <path d="M461.6,109.6l-54.9-43.3c-1.7-1.4-3.8-2.4-6.2-2.4c-2.4,0-4.6,1-6.3,2.5L194.5,323c0,0-78.5-75.5-80.7-77.7 c-2.2-2.2-5.1-5.9-9.5-5.9c-4.4,0-6.4,3.1-8.7,5.4c-1.7,1.8-29.7,31.2-43.5,45.8c-0.8,0.9-1.3,1.4-2,2.1c-1.2,1.7-2,3.6-2,5.7 c0,2.2,0.8,4,2,5.7l2.8,2.6c0,0,139.3,133.8,141.6,136.1c2.3,2.3,5.1,5.2,9.2,5.2c4,0,7.3-4.3,9.2-6.2L462,121.8 c1.2-1.7,2-3.6,2-5.8C464,113.5,463,111.4,461.6,109.6z"/> </svg> </div> <div class="svg-wrapper-2"> <svg class='svg-2' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z"/></svg> </div> </div> ]1
You can save yourself a lot of headaches using modern technologies, A <svg-icon> Web Component, supported in all modern browsers. I processed some of your <path> with https://yqnn.github.io/svg-path-editor/, to make them smaller. You don't need 0.nnn precision when you stuff a 512x512 viewBox in 40x40 pixels It does not matter where or when you execute customElements.define; all existing or new <svg-icon> will automagically upgrade. <style> div { display: flex; justify-content: center; width:100%; background:pink; } </style> <div> <svg-icon> <path fill="green" d="m461.6 109.6-54.9-43.3c-1.7-1.4-3.8-2.4-6.2-2.4-2.4 0-4.6 1-6.3 2.5l-199.7 256.6c0 0-78.5-75.5-80.7-77.7-2.2-2.2-5.1-5.9-9.5-5.9-4.4 0-6.4 3.1-8.7 5.4-1.7 1.8-29.7 31.2-43.5 45.8-.8.9-1.3 1.4-2 2.1-1.2 1.7-2 3.6-2 5.7 0 2.2.8 4 2 5.7l2.8 2.6c0 0 139.3 133.8 141.6 136.1 2.3 2.3 5.1 5.2 9.2 5.2 4 0 7.3-4.3 9.2-6.2l249.1-320c1.2-1.7 2-3.6 2-5.8 0-2.5-1-4.6-2.4-6.4z" /> </svg-icon> <svg-icon vb="5120"> <path fill="red" d="m4616 1096-549-433c-17-14-38-24-62-24-24 0-46 10-63 25l-1997 2566c0 0-785-755-807-777-22-22-51-59-95-59-44 0-64 31-87 54-17 18-297 312-435 458-8 9-13 14-20 21-12 17-20 36-20 57 0 22 8 40 20 57l28 26c0 0 1393 1338 1416 1361 23 23 51 52 92 52 40 0 73-43 92-62l2491-3200c12-17 20-36 20-58 0-25-10-46-24-64z" /> </svg-icon> <svg-icon vb="24"> <path fill="blue" d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z" /> </svg-icon> <svg-icon vb="24"> <path fill="rebeccapurple" d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z" /> </svg-icon> </div> <script> customElements.define("svg-icon", class extends HTMLElement { connectedCallback() { let vb = this.getAttribute("vb") || 512; setTimeout(() => { // wait till path innerHTML is parsed let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("viewBox",`0 0 ${vb} ${vb}`); svg.innerHTML = this.innerHTML; this.replaceWith(svg); // Web Component did its job, no need to keep it }); } }); </script> Take this idea some steps further and you get https://iconmeister.github.io
Hide/show toggle inside password input
So I was wondering how I could place a svg toggle button inside the input['password'] like this and when this is clicked, it would be displayed as this: I've made a simple version, with a checkbox, but I don't know how to do this with a svg, and make it toggle. The svg images I use is posted in the code. function togglePass() { var x = document.getElementById("login-form-password"); if (x.type === "password") { x.type = "text"; } else { x.type = "password"; } } <p class="signin_title">Sign in</p> <input type="text" id="login-form-username" name="os_username" placeholder="Username" required><br><br> <!-- Password --> <input type="password" id="login-form-password" name="os_password" placeholder="Password" required><br> <!-- An element to toggle between password visibility --> <input type="checkbox" onclick="togglePass()">Show Password <br><p>When password is hidden</p> <svg id="Layer_1" data-name="Layer 1" width="25" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>eye-glyph</title><path d="M320,256a64,64,0,1,1-64-64A64.07,64.07,0,0,1,320,256Zm189.81,9.42C460.86,364.89,363.6,426.67,256,426.67S51.14,364.89,2.19,265.42a21.33,21.33,0,0,1,0-18.83C51.14,147.11,148.4,85.33,256,85.33s204.86,61.78,253.81,161.25A21.33,21.33,0,0,1,509.81,265.42ZM362.67,256A106.67,106.67,0,1,0,256,362.67,106.79,106.79,0,0,0,362.67,256Z"/></svg><br> <p>When password is shown </p> <svg id="Layer_1" data-name="Layer 1" width="25" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>eye-disabled-glyph</title><path d="M409.84,132.33l95.91-95.91A21.33,21.33,0,1,0,475.58,6.25L6.25,475.58a21.33,21.33,0,1,0,30.17,30.17L140.77,401.4A275.84,275.84,0,0,0,256,426.67c107.6,0,204.85-61.78,253.81-161.25a21.33,21.33,0,0,0,0-18.83A291,291,0,0,0,409.84,132.33ZM256,362.67a105.78,105.78,0,0,1-58.7-17.8l31.21-31.21A63.29,63.29,0,0,0,256,320a64.07,64.07,0,0,0,64-64,63.28,63.28,0,0,0-6.34-27.49l31.21-31.21A106.45,106.45,0,0,1,256,362.67ZM2.19,265.42a21.33,21.33,0,0,1,0-18.83C51.15,147.11,148.4,85.33,256,85.33a277,277,0,0,1,70.4,9.22l-55.88,55.88A105.9,105.9,0,0,0,150.44,270.52L67.88,353.08A295.2,295.2,0,0,1,2.19,265.42Z"/></svg> So how exactly do I create this using svg? Thought about maybe if its possible to use the checkbox, and with some js look if its clicked or not, and change the svg depending on that? and then with some css, try to move it inside the input['password']? ` Something like this but with a svg input[type="text"] { width: 200px; height: 20px; padding-right: 50px; } input[type="submit"] { margin-left: -50px; height: 25px; width: 50px; background: blue; color: white; border: 0; -webkit-appearance: none; } <input type="text"><input type="submit" value="SVG">
First of all, make sure an id is only used once! (Second svg changed) You can hide and show the desired <svg> onclick Wrap the input and svg's into a container for styling I've moved the onclick to the <svg> var x = document.getElementById("login-form-password"); // Input var s = document.getElementById("Layer_1"); // Show pass var h = document.getElementById("Layer_2"); // Hide pass function togglePass() { if (x.type === "password") { x.type = 'text'; s.style.display = 'none'; h.style.display = 'inline'; } else { x.type = 'password'; s.style.display = 'inline'; h.style.display = 'none'; } } #inputcontainer { display: flex; } #inputcontainer > svg { margin-left: 5px; } <p class="signin_title">Sign in</p> <input type="text" id="login-form-username" name="os_username" placeholder="Username" required><br><br> <div id='inputcontainer'> <input type="password" id="login-form-password" name="os_password" placeholder="Password" required></input> <svg id="Layer_1" onclick="togglePass()" data-name="Layer 1" width="25" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>eye-glyph</title><path d="M320,256a64,64,0,1,1-64-64A64.07,64.07,0,0,1,320,256Zm189.81,9.42C460.86,364.89,363.6,426.67,256,426.67S51.14,364.89,2.19,265.42a21.33,21.33,0,0,1,0-18.83C51.14,147.11,148.4,85.33,256,85.33s204.86,61.78,253.81,161.25A21.33,21.33,0,0,1,509.81,265.42ZM362.67,256A106.67,106.67,0,1,0,256,362.67,106.79,106.79,0,0,0,362.67,256Z"/></svg> <svg id="Layer_2" onclick="togglePass()" data-name="Layer 2" width="25" xmlns="http://www.w3.org/2000/svg" style='display: none' viewBox="0 0 512 512"><title>eye-disabled-glyph</title><path d="M409.84,132.33l95.91-95.91A21.33,21.33,0,1,0,475.58,6.25L6.25,475.58a21.33,21.33,0,1,0,30.17,30.17L140.77,401.4A275.84,275.84,0,0,0,256,426.67c107.6,0,204.85-61.78,253.81-161.25a21.33,21.33,0,0,0,0-18.83A291,291,0,0,0,409.84,132.33ZM256,362.67a105.78,105.78,0,0,1-58.7-17.8l31.21-31.21A63.29,63.29,0,0,0,256,320a64.07,64.07,0,0,0,64-64,63.28,63.28,0,0,0-6.34-27.49l31.21-31.21A106.45,106.45,0,0,1,256,362.67ZM2.19,265.42a21.33,21.33,0,0,1,0-18.83C51.15,147.11,148.4,85.33,256,85.33a277,277,0,0,1,70.4,9.22l-55.88,55.88A105.9,105.9,0,0,0,150.44,270.52L67.88,353.08A295.2,295.2,0,0,1,2.19,265.42Z"/></svg> </div>
You have to get svg and input into the same div. And little bit Css magic! For example, HTML: <div class="inputCover"> <input type="password" class="input"> <svg class="icon"></svg> </div> CSS: .inputCover{ position: relative; //so, when do you make parent div position relative, then absolute items inside is not goes outside. } .input{ padding: 5px; padding-right: 25px; //you can limit input's inside, so text not goes to under of icon } .icon{ position: absolute; //you can make icon on the input like this. top: 50%; //icon will be center of the input from top to bottom. right: 10px; //right position. transform: translateY(-50%); //this is important to make icon perfectly centered. } I think this is the right answer for you.
You can try to place SVG inside input using absolute position, and then to add onclick event on SVG itself. Something like this: function togglePass() { var x = document.getElementById("login-form-password"); var l1 = document.getElementById("Layer_1"); var l2 = document.getElementById("Layer_2"); if (x.type === "password") { x.type = "text"; l1.setAttribute('hidden', true); l2.removeAttribute('hidden'); } else { x.type = "password"; l1.removeAttribute('hidden'); l2.setAttribute('hidden', true); } } [hidden] { display: none; } .form-group { position: relative; width: 170px; } .form-group svg { position: absolute; right: 10px; top: 2px; width: 16px; height: auto; } <p class="signin_title">Sign in</p> <input type="text" id="login-form-username" name="os_username" placeholder="Username" required><br><br> <!-- Password --> <div class="form-group"> <input type="password" id="login-form-password" name="os_password" placeholder="Password" required> <svg onclick="togglePass()" id="Layer_1" data-name="Layer 1" width="25" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>eye-glyph</title><path d="M320,256a64,64,0,1,1-64-64A64.07,64.07,0,0,1,320,256Zm189.81,9.42C460.86,364.89,363.6,426.67,256,426.67S51.14,364.89,2.19,265.42a21.33,21.33,0,0,1,0-18.83C51.14,147.11,148.4,85.33,256,85.33s204.86,61.78,253.81,161.25A21.33,21.33,0,0,1,509.81,265.42ZM362.67,256A106.67,106.67,0,1,0,256,362.67,106.79,106.79,0,0,0,362.67,256Z"/></svg> <svg onclick="togglePass()" hidden id="Layer_2" data-name="Layer 2" width="25" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>eye-disabled-glyph</title><path d="M409.84,132.33l95.91-95.91A21.33,21.33,0,1,0,475.58,6.25L6.25,475.58a21.33,21.33,0,1,0,30.17,30.17L140.77,401.4A275.84,275.84,0,0,0,256,426.67c107.6,0,204.85-61.78,253.81-161.25a21.33,21.33,0,0,0,0-18.83A291,291,0,0,0,409.84,132.33ZM256,362.67a105.78,105.78,0,0,1-58.7-17.8l31.21-31.21A63.29,63.29,0,0,0,256,320a64.07,64.07,0,0,0,64-64,63.28,63.28,0,0,0-6.34-27.49l31.21-31.21A106.45,106.45,0,0,1,256,362.67ZM2.19,265.42a21.33,21.33,0,0,1,0-18.83C51.15,147.11,148.4,85.33,256,85.33a277,277,0,0,1,70.4,9.22l-55.88,55.88A105.9,105.9,0,0,0,150.44,270.52L67.88,353.08A295.2,295.2,0,0,1,2.19,265.42Z"/></svg> </div>
<br> mucking up .next() in JQuery
I am hacking together an experimental pagination interface called wigi(board) but have run into an issue. The interface works by any l1 (subject) class or l2 (subheading) class running vertical down the left. Pages (l3 class nodes) are represented as points attached to the side of an l1 or l2. Mousing over any node will move the selector to that node and call a db query to display a specific page's contents. This works fine. It moves like it should. Right now I have buttons that will also move between the next and previous li in the navigation list. These are filler for future swiping and other interaction to demonstrate the issue. Right now these buttons work to a point, until the jquery .next() hits a <br> node, which I am using in order to break the l3 lines and continue the menu vertical to the next l1 or l2. When the .next hits the last node before one of these, it stops dead and wont jump down to the next row. Why? What is the best strategy to fix it? JS fiddle: http://jsfiddle.net/93g786jp/ The issue with next is in here. It is running over an li list (best to look at JSfiddle) function nextAndBack(e) { var cur = $('.dots .selected'), next = cur.next('li'), prev = cur.prev('li'); if (e.target.id == 'nextButton') { if (next.length == 1) { newSelected(next); console.log("Next Node:") console.log(next); $(next).trigger("mouseover"); } } else if (e.target.id == 'prevButton') { if (prev.length == 1) { newSelected(prev); console.log("Previous Node:") console.log(prev); $(prev).trigger("mouseover"); } } } Note this is based on the gooey interface by Lucas Bebber # https://codepen.io/lbebber/pen/lFdHu which was the closet match I could find for an interface like what I wanted. For the posted example, I stripped out any effects and other extras so some stubs exist.
As the <br /> gets in the way of selecting siblings you can instead use nextAll() or prevAll() and then get the first() of the selected items: next = cur.nextAll('li').first(), prev = cur.prevAll('li').first(); function wigiBoardMove() { var cur = $(this); var desty = cur.position().top; var destx = cur.position().left; var t = 0.6; gsap.to($(".select"), t, { y: desty, ease: Back.easeOut }); gsap.to($(".select"), t, { x: destx, ease: Back.easeOut }); newSelected(cur); } function newSelected(newTarget) { $('.selected').removeClass('selected'); newTarget.addClass('selected'); } function nextAndBack(e) { var cur = $('.dots .selected'), next = cur.nextAll('li').first(), prev = cur.prevAll('li').first(); if (e.target.id == 'nextButton') { if (next.length == 1) { newSelected(next); $(next).trigger("mouseover"); } } else if (e.target.id == 'prevButton') { if (prev.length == 1) { newSelected(prev); $(prev).trigger("mouseover"); } } } /* Modified from gooey pagnation code published by Lucas Bebber # https://codepen.io/lbebber/pen/lFdHu */ $(function() { $(".dot").on("mouseenter", wigiBoardMove); var lastPos = $(".select").position().top; function updateScale() { var pos = $(".select").position().top; var speed = Math.abs(pos - lastPos); var d = 44; var offset = -20; var hd = d / 2; var scale = (offset + pos) % d; if (scale > hd) { scale = hd - (scale - hd); } scale = 1 - ((scale / hd) * 0.35); gsap.to($(".select"), 0.1, { scaleY: 1 + (speed * 0.06), scaleX: scale }) lastPos = pos; requestAnimationFrame(updateScale); } requestAnimationFrame(updateScale); $(".dot:eq(0)").trigger("mouseover"); // Back and Forward Node Logic $('#nextButton, #prevButton').on('click', nextAndBack); }) #container {} .dots { list-style-type: none; padding: 0; margin: 0; padding-top: 20px; padding-bottom: 20px; padding-left: 20px; margin-left: -10px; padding-right: 10px; position: absolute; top: 0px; width: 150px; right: 0px; } .dot { display: inline-block; vertical-align: middle; margin-left: 5px; margin-right: 5px; cursor: pointer; color: white; position: relative; z-index: 2; } .l1 { border-radius: 100%; width: 10px; height: 10px; background: blue; border: none; } .l3 { border-radius: 100%; width: 7px; height: 7px; border: none; background: blue; } .select { display: block; border-radius: 100%; width: 15px; height: 15px; background: #daa520; position: absolute; z-index: 3; top: -4px; left: 1px; pointer-events: none; } <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.5.1/gsap.min.js"></script> <div id="container"> <ul class="dots"> <li class="select"></li> <li class="dot l1"></li> <li class="dot l3"></li> <li class="dot l3"></li> <li class="dot l3"></li><br> <li class="dot l1"></li> <li class="dot l3"></li> <li class="dot l3"></li><br> <li class="dot l1"></li> <li class="dot l3"></li><br> </ul> <img id="nextButton" height="10" width="10" alt="Next Node" /><br> <img id="prevButton" height="10" width="10" alt="Previous Node" /> </div>