Dynamically added svg viewbox values not working? [duplicate] - javascript

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

Related

Strange behavior when I use addEventListener() on click. It only happens at mobile devices

I'm gonna try to explain what is happening.
When I use a class "hide" as a display: none; to hide a button and show another button after click it works well in desktop browser, but when I tried the same thing at devices like iPhone, Tablets, etc. It has a strange behavior, after the second click, apparently it works ok, but the button don't change, it only changes when I click outside of it. If I don't do that looks like the button do not change. I have to click at the button one time to change them and apparently works, but the button only change when I click again outside it. It happens only in mobile devices.
I don't know what can I do to fix this in devices.
const buttonForest = document.querySelector(".button-forest");
const buttonForestSelected = document.querySelector(".button-forest-selected");
function forestButton() {
buttonForest.classList.remove("hide");
buttonForestSelected.classList.add("hide");
}
function forestButtonSelected() {
buttonForest.classList.add("hide");
buttonForestSelected.classList.remove("hide");
}
buttonForest.addEventListener("click", function() {
forestButtonSelected();
});
buttonForestSelected.addEventListener("click", function() {
forestButton();
});
:root {
font-size: 62.5%;
}
main {
display: flex;
align-self: center;
justify-self: center;
padding: 5rem;
}
button {
background-color: transparent;
border: none;
cursor: pointer;
position: relative;
display: flex;
align-items: end;
justify-content: center;
}
.button-container {
border: none;
position: relative;
display: flex;
align-items: end;
justify-content: center;
}
.icon-sound:hover .slider {
background: white;
}
.icon-sound:hover .slider::-webkit-slider-thumb {
background: white;
}
.icon-sound:hover .slider::-moz-range-thumb {
background: white;
}
.button-container {
border: none;
position: relative;
display: flex;
align-items: end;
justify-content: center;
}
.hide {
display: none;
}
.icon-sound svg path {
fill: #e1e1e6;
}
.icon-sound svg path+path {
fill: #323238;
}
.icon-sound:hover .cor1 {
fill: #02799d;
}
.icon-sound:hover .cor2 {
fill: white;
}
#sounds {
display: grid;
grid-template-columns: auto auto;
gap: 3.2rem;
grid-template-areas: "item1 item2" "item3 item4";
}
.button-forest-selected .cor1 {
fill: #02799d;
}
.button-forest-selected .cor2 {
fill: white;
}
<div class="button-container icon-sound">
<button class="button-forest">
<svg
width="138"
height="152"
viewBox="0 0 138 152"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
class="cor1"
d="M0 24C0 10.7452 10.7452 0 24 0H114C127.255 0 138 10.7452 138 24V128C138 141.255 127.255 152 114 152H24C10.7452 152 0 141.255 0 128V24Z"
fill="#E1E1E6"
/>
<path
class="cor2"
d="M51.8355 104V99.8H67.0329V87.2H60.8158C56.9934 87.2 53.7352 85.835 51.0411 83.105C48.347 80.375 47 77.0733 47 73.2C47 70.4 47.7599 67.8217 49.2796 65.465C50.7993 63.1083 52.8487 61.3933 55.4276 60.32C55.8421 56.82 57.3503 53.8917 59.9523 51.535C62.5543 49.1783 65.6053 48 69.1053 48C72.6053 48 75.6562 49.1783 78.2582 51.535C80.8602 53.8917 82.3684 56.82 82.7829 60.32C85.3618 61.3933 87.4112 63.1083 88.9309 65.465C90.4507 67.8217 91.2105 70.4 91.2105 73.2C91.2105 77.0733 89.8635 80.375 87.1694 83.105C84.4753 85.835 81.2171 87.2 77.3947 87.2H71.1776V99.8H87.0658V104H51.8355ZM60.8158 83H77.3947C80.0658 83 82.3454 82.0433 84.2336 80.13C86.1217 78.2167 87.0658 75.9067 87.0658 73.2C87.0658 71.24 86.5132 69.455 85.4079 67.845C84.3026 66.235 82.875 65.0333 81.125 64.24L78.9145 63.26L78.6382 60.81C78.3158 58.3367 77.2566 56.2833 75.4605 54.65C73.6645 53.0167 71.5461 52.2 69.1053 52.2C66.6645 52.2 64.5461 53.0167 62.75 54.65C60.9539 56.2833 59.8947 58.3367 59.5724 60.81L59.2961 63.26L57.0855 64.24C55.3355 65.0333 53.9079 66.235 52.8026 67.845C51.6974 69.455 51.1447 71.24 51.1447 73.2C51.1447 75.9067 52.0888 78.2167 53.977 80.13C55.8651 82.0433 58.1447 83 60.8158 83Z"
fill="#323238"
/>
</svg>
</button>
<button class="button-forest-selected hide">
<svg
width="138"
height="152"
viewBox="0 0 138 152"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
class="cor1"
d="M0 24C0 10.7452 10.7452 0 24 0H114C127.255 0 138 10.7452 138 24V128C138 141.255 127.255 152 114 152H24C10.7452 152 0 141.255 0 128V24Z"
fill="#E1E1E6"
/>
<path
class="cor2"
d="M51.8355 104V99.8H67.0329V87.2H60.8158C56.9934 87.2 53.7352 85.835 51.0411 83.105C48.347 80.375 47 77.0733 47 73.2C47 70.4 47.7599 67.8217 49.2796 65.465C50.7993 63.1083 52.8487 61.3933 55.4276 60.32C55.8421 56.82 57.3503 53.8917 59.9523 51.535C62.5543 49.1783 65.6053 48 69.1053 48C72.6053 48 75.6562 49.1783 78.2582 51.535C80.8602 53.8917 82.3684 56.82 82.7829 60.32C85.3618 61.3933 87.4112 63.1083 88.9309 65.465C90.4507 67.8217 91.2105 70.4 91.2105 73.2C91.2105 77.0733 89.8635 80.375 87.1694 83.105C84.4753 85.835 81.2171 87.2 77.3947 87.2H71.1776V99.8H87.0658V104H51.8355ZM60.8158 83H77.3947C80.0658 83 82.3454 82.0433 84.2336 80.13C86.1217 78.2167 87.0658 75.9067 87.0658 73.2C87.0658 71.24 86.5132 69.455 85.4079 67.845C84.3026 66.235 82.875 65.0333 81.125 64.24L78.9145 63.26L78.6382 60.81C78.3158 58.3367 77.2566 56.2833 75.4605 54.65C73.6645 53.0167 71.5461 52.2 69.1053 52.2C66.6645 52.2 64.5461 53.0167 62.75 54.65C60.9539 56.2833 59.8947 58.3367 59.5724 60.81L59.2961 63.26L57.0855 64.24C55.3355 65.0333 53.9079 66.235 52.8026 67.845C51.6974 69.455 51.1447 71.24 51.1447 73.2C51.1447 75.9067 52.0888 78.2167 53.977 80.13C55.8651 82.0433 58.1447 83 60.8158 83Z"
fill="#323238"
/>
</svg>
</button>
</div>

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();

How to start and stop execution of a JavaScript script with two buttons?

I am animating an element in SVG so that it travels around a circular track. It runs around the track 3x and stops. I am using the GSAP MotionPath plugin to do the animation. I want to control the motion with start and stop buttons. The stop button can either pause the motion or stop it completely and have the element return to its place - whichever involves the simpler method.
I managed to get the animation to start by clicking the "START" button. But I can't get it to stop by clicking the "STOP" button.
I show below the animation with the start button.
function myFunction(){
gsap.registerPlugin(MotionPathPlugin);
gsap.to("#comet-horizontal", {
duration: 5,
repeat: 2,
repeatDelay: 0,
yoyo: false,
ease: "none",
motionPath:{
path: "#racetrack",
align: "#racetrack",
autoRotate: true,
alignOrigin: [0.5, 0.5]
}
});
}
html, body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
body {
/*background-color: black;*/
min-height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
svg {
overflow: visible;
height: 100%;
background-color: orange;
/* Fix Safari rendering bug */
transform: translateZ(0);
}
circle {
fill: pink;
}
#button{
width: 60px;
height: 30px;
background-color: orange;
position: relative;
margin-top: 5px;
margin-bottom: 5px;
}
#button2{
width: 60px;
height: 30px;
background-color: yellow;
position: relative;
margin-top: 5px;
margin-bottom: 5px;
}
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.3/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.3/MotionPathPlugin.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="snap.svg-min.js"></script>
</head>
<body>
<button id="button" onclick="myFunction()">START</button>
<button id="button2">STOP</button>
<svg
width="100%"
height="100%"
viewBox="0 0 338.66667 190.5">
<path
id="racetrack"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffaaaa;stroke-width:2.32673;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;stop-color:#000000;stop-opacity:1"
d="M 184.04496,79.375006 A 51.753304,51.753307 0 0 1 132.29166,131.1283 51.753304,51.753307 0 0 1 80.538365,79.375006 51.753304,51.753307 0 0 1 132.29166,27.621698 a 51.753304,51.753307 0 0 1 51.7533,51.753308 z" />
<path
id="comet-horizontal"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#00d3ff;fill-opacity:1;fill-rule:evenodd;stroke-width:2.82278;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
d="m 241.75652,61.794127 v 2.645839 l -13.22916,-0.661459 v -0.66146 -0.66146 z"
sodipodi:nodetypes="cccccc" />
</svg>
</body>
I browsed StackOverflow but could not find the answer. For example, How to control the execution of javascript functions? is a similar question to mine but the asker wants a new function to start after the first one is stopped.
I cannot use typical animation functions such as animation-play-state since the animation is using a GreenSock script.
I also tried out the top solution from How to stop a function during its execution - JavaScript but it didn't work for me (maybe because I implemented it wrongly). I am having trouble writing the "if" condition for when the Stop button is clicked. I tried if(document.getElementById('button').clicked == true) but it didn't work.
Below is the solution mentioned above in the last link.
function foo1(){
console.log("Foo started...");
if(prompt("Type 1 to terminate right now or anything else to continue...") == "1"){
return; // Function will terminate here if this is encountered
}
console.log("Foo ending..."); // This will only be run if something other than 1 was entered
}
foo1();
I would prefer to stick with vanilla JS solutions if possible rather than JQuery because I am not familiar with JQuery, but if a JQuery solution is the easiest way of doing it, I am open to it.
This is the attempt to implement the "foo1" solution that failed:
function myFunction(){
gsap.registerPlugin(MotionPathPlugin);
gsap.to("#comet-horizontal", {
duration: 5,
repeat: 2,
repeatDelay: 0,
yoyo: false,
ease: "none",
motionPath:{
path: "#racetrack",
align: "#racetrack",
autoRotate: true,
alignOrigin: [0.5, 0.5]
}
});
if (document.getElementById("button2").clicked == true)
return;
}
myFunction();
The gsap.to method returns a Tween object with which you can control the animation. It has pause, resume, restart and several other useful methods.
Here I adapted your script:
Renamed the HTML buttons with more telling names.
Attached the click handlers in code, not via HTML attribute
Added a global variable to allow each event handler to access the above mentioned object
Added the logic to pause and resume the animation
Change the repeat: 2 parameter to repeat: -1, so the animation has no end.
let tween; // global so both handlers can access it
document.getElementById("buttonStart").addEventListener("click", function () {
if (tween) { // Not first time:
tween.resume(); // Continue from where it was paused
return;
}
gsap.registerPlugin(MotionPathPlugin);
tween = gsap.to("#comet-horizontal", {
duration: 5,
repeat: -1,
repeatDelay: 0,
yoyo: false,
ease: "none",
motionPath:{
path: "#racetrack",
align: "#racetrack",
autoRotate: true,
alignOrigin: [0.5, 0.5]
}
});
});
document.getElementById("buttonStop").addEventListener("click", function () {
tween?.pause();
});
html, body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
body {
/*background-color: black;*/
min-height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
svg {
overflow: visible;
height: 100%;
background-color: orange;
/* Fix Safari rendering bug */
transform: translateZ(0);
}
circle {
fill: pink;
}
#button{
width: 60px;
height: 30px;
background-color: orange;
position: relative;
margin-top: 5px;
margin-bottom: 5px;
}
#button2{
width: 60px;
height: 30px;
background-color: yellow;
position: relative;
margin-top: 5px;
margin-bottom: 5px;
}
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.3/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.3/MotionPathPlugin.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="snap.svg-min.js"></script>
</head>
<body>
<button id="buttonStart">START</button>
<button id="buttonStop">STOP</button>
<svg
width="100%"
height="100%"
viewBox="0 0 338.66667 190.5">
<path
id="racetrack"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffaaaa;stroke-width:2.32673;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;stop-color:#000000;stop-opacity:1"
d="M 184.04496,79.375006 A 51.753304,51.753307 0 0 1 132.29166,131.1283 51.753304,51.753307 0 0 1 80.538365,79.375006 51.753304,51.753307 0 0 1 132.29166,27.621698 a 51.753304,51.753307 0 0 1 51.7533,51.753308 z" />
<path
id="comet-horizontal"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#00d3ff;fill-opacity:1;fill-rule:evenodd;stroke-width:2.82278;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
d="m 241.75652,61.794127 v 2.645839 l -13.22916,-0.661459 v -0.66146 -0.66146 z"
sodipodi:nodetypes="cccccc" />
</svg>
</body>

How to avoid duplicate drag in mxgraph

I want to avoid duplicate drop on mxgraph canvas.
let say I have dragged Pipe on canvas 2nd time it should not allow it be dragged on canvas.
Question: how to avoid duplicate drop on canvas
Here is my working code drag with duplicate allowed
Drag and Drop
var graph = {};
function initCanvas() {
//This function is called onload of body itself and it will make the mxgraph canvas
graph = new mxGraph(document.getElementById('graph-wrapper'));
graph.htmlLabels = true;
graph.cellsEditable = false;
// render as HTML node always. You probably won't want that in real world though
graph.convertValueToString = function(cell) {
return cell.value;
}
const createDropHandler = function (cells, allowSplit) {
return function (graph, evt, target, x, y) {
const select = graph.importCells(cells, x, y, target);
graph.setSelectionCells(select);
};
};
const createDragPreview = function (width, height) {
var elt = document.createElement('div');
elt.style.border = '1px dashed black';
elt.style.width = width + 'px';
elt.style.height = height + 'px';
return elt;
};
const createDragSource = function (elt, dropHandler, preview) {
return mxUtils.makeDraggable(elt, graph, dropHandler, preview, 0, 0, graph.autoscroll, true, true);
};
const createItem = (id) => {
const elt = document.getElementById(id);
const width = elt.clientWidth;
const height = elt.clientHeight;
const cell = new mxCell('', new mxGeometry(0, 0, width, height), 'fillColor=none;strokeColor=none');
cell.vertex = true;
graph.model.setValue(cell, elt);
const cells = [cell];
const bounds = new mxRectangle(0, 0, width, height);
createDragSource(elt, createDropHandler(cells, true, false, bounds), createDragPreview(width, height), cells, bounds);
};
createItem("shape_1");
createItem("shape_2");
createItem("shape_3");
}
#graph-wrapper {
background: #333;
width: 100%;
height: 528px;
}
<html>
<head>
<title>Toolbar example for mxGraph</title>
<script type="text/javascript">
mxBasePath = 'https://jgraph.github.io/mxgraph/javascript/src';
</script>
<script src="https://jgraph.github.io/mxgraph/javascript/src/js/mxClient.js"></script>
<script src="./app.js"></script>
</head>
<body onload="initCanvas()">
<h4>Drag same box 2 times on the canvas. see duplicate is allowed</h4>
<div>
<div id="shape_1"
style="width: 100px; height: 100px; border-radius: 50%; background: red; display: inline-flex; text-align: center; color: #fff; align-items: center; justify-content: center;">
Pipe
</div>
<div draggable="true" id="shape_2"
style="width: 100px; height: 100px; border-radius: 5%; background: orange; display: inline-flex; text-align: center; color: #fff; align-items: center; justify-content: center;">
Team
</div>
<div draggable="true" id="shape_3"
style="width: 100px; height: 64px; background: #009688; display: inline-flex; text-align: center; color: #fff; align-items: center; justify-content: center; border-radius: 207px; flex-direction: column;">
<div> <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" />
</svg></div>
<div>Info</div>
</div>
</div>
<div id="graph-wrapper">
</div>
</body>
</html>
You just need check all current cells value of maxgarph in createDropHandler. So get all cells by graph.getModel().cells and then check to see if the cells you want to add exists or not:
let allcells = graph.getModel().cells;
for (var i in allcells)
if (allcells[i].value && (allcells[i].value.id == cells[0].value.id))
return;
var graph = {};
function initCanvas() {
//This function is called onload of body itself and it will make the mxgraph canvas
graph = new mxGraph(document.getElementById('graph-wrapper'));
graph.htmlLabels = true;
graph.cellsEditable = false;
// render as HTML node always. You probably won't want that in real world though
graph.convertValueToString = function (cell) {
return cell.value;
}
const createDropHandler = function (cells, allowSplit) {
return function (graph, evt, target, x, y) {
debugger
let allcells = graph.getModel().cells;
for (var i in allcells)
if (allcells[i].value && (allcells[i].value.id == cells[0].value.id))
return;
const select = graph.importCells(cells, x, y, target);
graph.setSelectionCells(select);
};
};
const createDragPreview = function (width, height) {
var elt = document.createElement('div');
elt.style.border = '1px dashed black';
elt.style.width = width + 'px';
elt.style.height = height + 'px';
return elt;
};
const createDragSource = function (elt, dropHandler, preview) {
return mxUtils.makeDraggable(elt, graph, dropHandler, preview, 0, 0, graph.autoscroll, true, true);
};
const createItem = (id) => {
const elt = document.getElementById(id);
const width = elt.clientWidth;
const height = elt.clientHeight;
const cell = new mxCell('', new mxGeometry(0, 0, width, height), 'fillColor=none;strokeColor=none');
cell.vertex = true;
graph.model.setValue(cell, elt);
const cells = [cell];
const bounds = new mxRectangle(0, 0, width, height);
createDragSource(elt, createDropHandler(cells, true, false, bounds), createDragPreview(width, height), cells, bounds);
};
createItem("shape_1");
createItem("shape_2");
createItem("shape_3");
}
<html>
<head>
<title>Toolbar example for mxGraph</title>
<script type="text/javascript">
mxBasePath = 'https://jgraph.github.io/mxgraph/javascript/src';
</script>
<script src="https://jgraph.github.io/mxgraph/javascript/src/js/mxClient.js"></script>
<script src="./app.js"></script>
<style>
#graph-wrapper {
background: #333;
width: 100%;
height: 528px;
}
</style>
</head>
<body onload="initCanvas()">
<h4>Drag same box 2 times on the canvas. see duplicate is allowed</h4>
<div>
<div id="shape_1"
style="width: 100px; height: 100px; border-radius: 50%; background: red; display: inline-flex; text-align: center; color: #fff; align-items: center; justify-content: center;">
Pipe
</div>
<div draggable="true" id="shape_2"
style="width: 100px; height: 100px; border-radius: 5%; background: orange; display: inline-flex; text-align: center; color: #fff; align-items: center; justify-content: center;">
Team
</div>
<div draggable="true" id="shape_3"
style="width: 100px; height: 64px; background: #009688; display: inline-flex; text-align: center; color: #fff; align-items: center; justify-content: center; border-radius: 207px; flex-direction: column;">
<div>
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" />
</svg>
</div>
<div>Info</div>
</div>
</div>
<div id="graph-wrapper">
</div>
First of all, I don't have any idea about mxgraph. To solve your problem I made some research on documentation of the library and I didn't find any tips.
I used the basics of the Javascript to achieve this and I didn't touch any of your main code and flow. I just add the draggable_status to graph element on createDropHandler function.
When you drag once, the below code add a status to the object and you can't drag anymore just adding a simple control.
graph.draggable_status = false;
To control, should be in the same function - const createDropHandler = function (cells, allowSplit)
if(graph.draggable_status != false){
const select = graph.importCells(cells, x, y, target);
graph.setSelectionCells(select);
graph.draggable_status = false;
}
The full code snippet is here;
var graph = {};
function initCanvas() {
//This function is called onload of body itself and it will make the mxgraph canvas
graph = new mxGraph(document.getElementById('graph-wrapper'));
graph.htmlLabels = true;
graph.cellsEditable = false;
// render as HTML node always. You probably won't want that in real world though
graph.convertValueToString = function(cell) {
return cell.value;
}
const createDropHandler = function (cells, allowSplit) {
return function (graph, evt, target, x, y) {
if(graph.draggable_status != false){
const select = graph.importCells(cells, x, y, target);
graph.setSelectionCells(select);
graph.draggable_status = false;
}
};
};
const createDragPreview = function (width, height) {
var elt = document.createElement('div');
elt.style.border = '1px dashed black';
elt.style.width = width + 'px';
elt.style.height = height + 'px';
return elt;
};
const createDragSource = function (elt, dropHandler, preview) {
return mxUtils.makeDraggable(elt, graph, dropHandler, preview, 0, 0, graph.autoscroll, true, true);
};
const createItem = (id) => {
const elt = document.getElementById(id);
const width = elt.clientWidth;
const height = elt.clientHeight;
const cell = new mxCell('', new mxGeometry(0, 0, width, height), 'fillColor=none;strokeColor=none');
cell.vertex = true;
graph.model.setValue(cell, elt);
const cells = [cell];
const bounds = new mxRectangle(0, 0, width, height);
createDragSource(elt, createDropHandler(cells, true, false, bounds), createDragPreview(width, height), cells, bounds);
};
createItem("shape_1");
createItem("shape_2");
createItem("shape_3");
}
#graph-wrapper {
background: #333;
width: 100%;
height: 528px;
}
<html>
<head>
<title>Toolbar example for mxGraph</title>
<script type="text/javascript">
mxBasePath = 'https://jgraph.github.io/mxgraph/javascript/src';
</script>
<script src="https://jgraph.github.io/mxgraph/javascript/src/js/mxClient.js"></script>
<script src="./app.js"></script>
</head>
<body onload="initCanvas()">
<h4>Drag same box 2 times on the canvas. see duplicate is allowed</h4>
<div>
<div id="shape_1"
style="width: 100px; height: 100px; border-radius: 50%; background: red; display: inline-flex; text-align: center; color: #fff; align-items: center; justify-content: center;">
Pipe
</div>
<div draggable="true" id="shape_2"
style="width: 100px; height: 100px; border-radius: 5%; background: orange; display: inline-flex; text-align: center; color: #fff; align-items: center; justify-content: center;">
Team
</div>
<div draggable="true" id="shape_3"
style="width: 100px; height: 64px; background: #009688; display: inline-flex; text-align: center; color: #fff; align-items: center; justify-content: center; border-radius: 207px; flex-direction: column;">
<div> <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" />
</svg></div>
<div>Info</div>
</div>
</div>
<div id="graph-wrapper">
</div>
</body>
</html>

Offset div based on image width

I'm trying to offset the element containing the image which has 21 frames. 0 - 21. I've placed 21 vertical columns over the image to visualize which frame should be present when the user's cursor is within the column lines. So each time your cursor moves into a different column of the grid, it should display a new frame. I need help figuring out whey the last frame (20) only shows when the user's cursor is on the very last pixel to the far right of the frame?
All the work is done in the javascript. I've commented each step and print to the console useful information regarding the math.
https://jsfiddle.net/JokerMartini/2e9awc4u/67/
window.onload = function() {
console.log('go')
$("#viewport").mousemove(function(e) {
// step 0: value to offset each frame (without scale)
const frameWidth = 320
// step 1: get the current mouse position in relation to the current element
const x = e.offsetX
// step 3: get width of viewable content, subtract 1 pixel starts at 0px
const viewWidth = $("#viewport").width() - 1
// step 4: find the % of the current position (in decimals 0-1.0)
const percent = x / viewWidth
// step 5: find the frame by the current percentage
const filmstripWidth = $("#filmstrip").width()
const frameByPercent = Math.round((filmstripWidth - frameWidth) * percent)
// step 6: find the nearest multiplier to frameWidth to offset
const offset = Math.floor(frameByPercent / frameWidth) * frameWidth
// const offset = -frameByPercent // smooth
// step 7: set that as the current position in negative (for offset reasons)
$("#filmstrip").css('transform', 'translate(' + -offset + 'px)')
console.log(
'CURSOR:', x,
'VIEW:', viewWidth,
'PERCENT:', percent,
'IMAGE WIDTH:', filmstripWidth,
frameByPercent
)
});
};
html {
height: 100%;
width: 100%;
}
#filmstrip {
will-change: transform;
pointer-events:none;
}
#margin-center {
background: grey;
padding: 30px
}
#viewport {
height: 180px;
width: 320px;
background: #FFFFAA;
display: block;
margin: auto;
position: relative;
overflow: hidden; /* Comment for debugging */
}
#guides {
position: absolute;
top: 0;
left: 0;
pointer-events:none;
}
#content {
display: inline-block;
font-size: 0;
height: auto;
max-width: 400px;
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="content">
<div id="margin-center">
<div id='viewport'>
<img id='filmstrip' src="https://i.ibb.co/7XDpcnd/timer.jpg" width="auto" height="180">
<svg id="guides" width="320px" height="180px">
<defs>
<pattern id="grid" width="15.238" height="180" patternUnits="userSpaceOnUse">
<path d="M 16 0 L 0 0 0 180" fill="none" stroke="black" stroke-width="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
</svg>
</div>
</div>
</div>
Your results are offset by 1 because you deducted one full frameWidth.
Added code to cap percent at 0.999, to prevent it jumping to 22nd frame. mousemove positions will sometimes be at end position or greater.
window.onload = function() {
console.log('go')
$("#viewport").mousemove(function(e) {
// step 0: value to offset each frame (without scale)
const frameWidth = 320
// step 1: get the current mouse position in relation to the current element
const x = e.offsetX
// step 3: get width of viewable content, subtract 1 pixel starts at 0px
const viewWidth = $("#viewport").width() - 1
// step 4: find the % of the current position (in decimals 0-1.0)
const percent = x / viewWidth
// step 5: find the frame by the current percentage
const filmstripWidth = $("#filmstrip").width()
const frameByPercent = Math.round((filmstripWidth) * Math.min(percent,0.999))
// step 6: find the nearest multiplier to frameWidth to offset
const offset = Math.floor(frameByPercent / frameWidth) * frameWidth
// const offset = -frameByPercent // smooth
// step 7: set that as the current position in negative (for offset reasons)
$("#filmstrip").css('transform', 'translate(' + -offset + 'px)')
console.log(
'CURSOR:', x,
'VIEW:', viewWidth,
'PERCENT:', percent,
'IMAGE WIDTH:', filmstripWidth,
frameByPercent
)
});
};
html {
height: 100%;
width: 100%;
}
#filmstrip {
will-change: transform;
pointer-events:none;
}
#margin-center {
background: grey;
padding: 30px
}
#viewport {
height: 180px;
width: 320px;
background: #FFFFAA;
display: block;
margin: auto;
position: relative;
overflow: hidden; /* Comment for debugging */
}
#guides {
position: absolute;
top: 0;
left: 0;
pointer-events:none;
}
#content {
display: inline-block;
font-size: 0;
height: auto;
max-width: 400px;
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="content">
<div id="margin-center">
<div id='viewport'>
<img id='filmstrip' src="https://i.ibb.co/7XDpcnd/timer.jpg" width="auto" height="180">
<svg id="guides" width="320px" height="180px">
<defs>
<pattern id="grid" width="15.238" height="180" patternUnits="userSpaceOnUse">
<path d="M 16 0 L 0 0 0 180" fill="none" stroke="black" stroke-width="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
</svg>
</div>
</div>
</div>

Categories