Safari doesn't emit pointer events on overflowing SVG contents - javascript

I'm trying to add click events to an SVG Element which has visible overflow and a shape element(circle/path) inside it which overflows the SVG.
On Safari(9,10,11), click event doesn't work in the area where the shape element(circle/path) overflows while it works fine in the area present within the SVG.
var count = 0;
function clickMe() {
console.log("in click func");
count++;
document.getElementById("counter").innerHTML = count;
}
#counter {
font-size: 2em;
}
#starSvg {
pointer-events: auto;
overflow: visible;
position: absolute;
left: 200px;
top: 250px;
border: 1px solid red;
}
#starPolygon {
overflow: visible;
fill: rgba(0, 153, 219, 1);
pointer-events: visiblePainted;
stroke-width: 4;
stroke-linecap: square;
stroke-linejoin: round;
stroke: rgba(219, 0, 153, 1);
cursor: pointer;
shape-rendering: geometricPrecision
}
p {
margin: 10px 0;
}
<div>
<p>Open this webpage on Chrome & safari</p>
<p>On Chrome: Click work on all four hands of the star.</p>
<p>On Safari: Click works only on the hands inside the red area(SVG bounding Rect).</p>
<p style="position: absolute; top: 100px; left: 200px;">Click Event Counter:
<span id="counter">0</span>
</p>
<div class="containter">
<svg onclick="clickMe()" id="starSvg" width="100%" height="100%">
<g width="100%" height="100%" transform="" style="overflow: visible; transform: rotate(45deg) translate(0, 0);">
<polygon id="starPolygon" shape-rendering="geometricPrecision" points="0 -90,15 -15,90 0,15 15,0 90,-15 15,-90 0,-15 -15"></polygon>
</g>
</svg>
</div>
</div>
Is this also a bug in Safari? The click event works fine in all browsers including IE.

This mirrors Webkit bug report #140723 here: https://bugs.webkit.org/show_bug.cgi?id=140723
That bug report links to this codepen example reproducing the exact same results you've found:
https://codepen.io/anon/pen/pvPQqY
And with the fix as I've described below applied here:
https://codepen.io/anon/pen/YeOmGW
===========
Edited for clarity: The evidence is clear that the initial clipping is taking effect at the outer most view port (svg) boundary, while at the same time the overflow property is allowing the visibility of the shape outside that clipped area. In effect rendering the pointer events void.
Said differently, evidence that clipping is applied IAW the first sentence here: http://w3.org/TR/SVG11/masking.html#AutoClipAtViewportNotViewBox , while at the same time conforming to the overflow rules (last three) here: http://w3.org/TR/SVG11/masking.html#OverflowAndClipProperties is the cause of this issue with Safari;
To overcome this issue, the OP must create a wrapper viewport to create a (workaround) solution to the dilemma created by the inconsistent implementations.
This is similar to adding any other HTML structure that might be required to wrap required content. (I'm really focused on helping solve the problem)
Hope this helps.
var count = 0;
document.querySelector('svg').addEventListener('click',clickMe);
function clickMe(e) {
console.log("clicked on: " + e.target.id);
count++;
document.getElementById("counter").innerHTML = count;
}
#counter {
font-size: 2em;
}
#starSvg {
pointer-events: auto;
position: absolute;
width: 100%;
height: 100%;
left:0;
top:0;
}
#starPolygon {
transform: translate(50%, 50%) rotate(45deg);
fill: rgba(0, 153, 219, 1);
stroke-width: 4;
stroke-linecap: square;
stroke-linejoin: round;
stroke: rgba(219, 0, 153, 1);
cursor: pointer;
shape-rendering: geometricPrecision
}
#starClip {
width: 100%;
height: 100%;
stroke-width: 1;
stroke: red;
fill: transparent;
}
p {
margin: 10px 0;
}
<div>
<p>Open this webpage on Chrome & safari</p>
<p>On Chrome: Click work on all four hands of the star.</p>
<p>On Safari: Click works only on the hands inside the red area(SVG bounding Rect).</p>
<p style="position: absolute; top: 100px; left: 200px;">Click Event Counter:
<span id="counter">0</span>
</p>
<div class="containter">
<svg id="starSvg">
<rect id="starClip" x="50%" y="50%"></rect>
<g>
<polygon id="starPolygon" x="0" y="0" points="0 -90,15 -15,90 0,15 15,0 90,-15 15,-90 0,-15 -15"></polygon>
</g>
</svg>
</div>
</div>

Seems to be fixed in version 11.1 of Safari.

Related

SVG tags don't seem sometimes while working with Nodejs

I had a problem with SVG tags not displaying sometimes. I refresh the page, it doesn't seem; I refresh two more times, it seems; I refresh again and it doesn't seem again. I had a little research and I've got that I need to add defer attribute to the script tag. (So, I think it was a problem that originated from the script's being executed before the page fully loaded.)
Now I add the NodeJs to the project and I've started to get this problem again. I've tried to send the HTML file by both app.get() method (express) and fs module, both don't work. How can I fix this problem?
My NodeJs code:
var http = require('http')
var express = require("express")
var app = express()
app.use(express.static(__dirname + '/public'));
app.get("/",(req,res)=>{
res.sendFile(__dirname+"/index.html")
})
var port = 3000;
app.listen(port,() => {
console.log(`The server is initialized on port ${port}.`)
})
The part of the code I use SVG (I use them as a border)
<button data-title="Add">
<svg xmlns="http://www.w3.org/2000/svg">
<rect pathLength="305"></rect>
</svg>
</button>
The CSS part (CSSs are in another file)
button {
padding: 24px 0;
font-size: 1em;
font-family: 'Open Sans', sans-serif !important;
color:rgba(255,255,255,0.7);
border:none;
position: relative;
outline: none;
background: none;
cursor: pointer;
transition: 0.4s;
&::after {
content: attr(data-title);
position: absolute; top:0; left: 0;
width: 100%; height: 100%;
display: flex;
justify-content: center; align-items: center;
transition: 0.4s;
}
&:hover::after {
text-shadow: 0 0 4px hsla(0, 0%, 100%, 0.4);
}
&:hover svg rect {
fill: rgba(255,255,255,0.1);
}
svg, svg rect {
position: absolute;
width: 100%; height: 100%;
top:0;left: 0;
fill: transparent;
overflow: initial;
}
svg rect {
stroke: #fff;
stroke-width: 1;
stroke-dasharray: 2 4;
stroke-linecap: butt;
stroke-opacity: 0.8;
rx: 25;
transition: 0.2s;
}
}
I would recommend the MDN SVG Tutorial for more information on using the <rect/> element; it's such a wonderful resource: https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Basic_Shapes
Below is a snippet of HTML using the <rect/> svg element:
File: index.html
<html>
<body>
<svg xmlns="http://www.w3.org/2000/svg">
<rect x="10" y="10" width="100" height="100"/>
</svg>
</body>
</html>
I believe the problem you are seeing is that you have not specified the height and width of the rectangle so nothing is rendered. I am also unfamiliar with the pathLength attribute, but you may not need it.
Please let me know if this helps a bit ^^

Circle around link on hover

I'm having troubles figuring how to make a circle doodle(like a hand drawn sketch) appear on a link when hovered. In a perfect world it should be an animated svg path, but at this point just to appear works for me. Here is what exactly I'm trying to achieve:
I've tried with background-image set to opacity:0 and when hover on opacity:1, but the issue is that when the link is longer the background image doesn't cover it all. Also I've tried with borders, but then I can't add a custom border shape, to look like a circle sketch you do with a pen.
Here is an example I found online: click here , the "Circle Me" example, under "Highlighted headlines"
I hope that this all makes sense,
Thank you!
You can learn how to do that by using Chrome DevTools or other similars from your reference site.
<div class='button'>
<button>Click Me</button>
<svg preserveAspectRatio="none">
<path fill="none" d="..." />
</svg>
</div>
.button {
position: relative;
display: inline-block;
cursor: pointer;
}
.button button {
padding: 8px 16px;
border: none;
background: none;
outline: none;
}
.button svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.button path {
stroke: #db3157;
stroke-width: 8px;
stroke-dasharray: 0 1500;
}
.button:hover path {
animation: draw 1s forwards;
}
#keyframes draw {
from {
stroke-dasharray: 0 1500;
}
to {
stroke-dasharray: 1500 1500;
}
}
Example on JSFiddle
HTMl
<html>
<head>
<style>
</style>
</head>
<body>
Test
</body>
</html>
CSS
.circle{
width:50px;
height:50px;
padding: 4em 4em;
}
.circle:hover {
border-radius: 100%;
background: green;
display:inline-block;
line-height:100px;
width:50px;
height:50px;
}
Just try this and let me know if it is working for you. I wanted to help you.

How can I center my clip-path's text relatively to the viewport and display all the clip-path's text?

Here my React component demo: https://codesandbox.io/s/epic-brown-osiq1, I am now using the viewBox's values, getBBox and getBoundingClientRect() to realize some calculations in order to position my element. Currently I have entered raw value based on the return the console have provided me from the getBoundingClientRect()'s logs. You can see it on the element I have implemented the getBoundingClientRect() on, namely the <svg>'s root element and the clip-path's text's element. Better but the text is more place tower the center of the screen that really aligned on center of the text's box-you can see the "So"'s word is at the start of the "Food"'s word instead of being aligned on the box's center. So I am at this point currently. Thanks for the feedback.*
note: You will see some comments providing information or parts of my former trials inside the sandbox.
What my code does ? concretely I display a clip-path's text with some animated panel travelling the clip-path - this is the color_panel_group's element- giving some dynamic to the composition.There is also a shadow behind the text to give some depth to the composition.
Expectation: display a clip-path's text responsively positioned at the vertical and horizontal's centers of the viewport.
Problem: My clip-path hides a part of the text and my trials to center the element relative to viewport fails to be fructuous.
What I have tried: I have tried to play with the width of the element and the x's positions of the element -mainly text, clip-path, symbol and both case. Even tried to play with the use element by implementing some class in it, but at the end of the day very approximative result outcomed. Also In tspan and symbol I have tried to play with x's attribute, again with very approximative outcomes. I have tried to play with position absolute and a relative container -mainly on the SVG's CSS selector directly-, still with approximative outcomes.
I am wondering what I am missing. Maybe someone can bring some explanation on my code's behavior?
Here my second presentation's resulting code (approximately what React component produces):
body {
background: orange;
}
svg {
background: green;
display: block;
margin: auto;
position: relative;
z-index: 2;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}
.component {
min-width: 100vw;
min-height: 100vh;
font-size: 100%;
}
.fade_in_background {
opacity: 1;
visibility: visible;
transition: all 1.5s ease-out 0s;
}
.brandtype {
margin: auto;
text-align: center;
}
.brandtype_use {
position: absolute;
transform: translate(-112.65px, 0)
}
.clipPath_text {
text-align: center;
}
.color_panel_group {
padding: 25px;
}
.shape_animation {
transform-origin: 0;
transform: scale(0, 1) translate(0, 0);
animation: moving-panel 3s 1.5s 1 alternate forwards;
}
.shadow {
transform: translate(10px, 10px)
}
.shape_animation_shadow {
fill: black;
fill-opacity: .505;
transition: all 1.3s ease-out 0.3s;
}
.brandtype {
font-size: 6.8em;
}
#keyframes moving-panel {
to {
transform: scale(1, 1) translate(20px, 0);
}
}
<div class="component">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 965 657">
<defs>
<symbol id="panel_animation" y="0">
<clipPath class="clipPath_text" id="clipPath_text"><text class="brandtype" word-spacing="-.45em">
<tspan x="0%" y="50%" dy="1.6em">So</tspan>
<tspan x="0%" y="50%" dy="3em">Food</tspan>
</text></clipPath>
<g class="shadow" clip-path="url(#clipPath_text)">
<rect class="shape_animation shape_animation_shadow" width="100%" height="100%" x="-25px">
</rect>
</g>
<g class="color_panel_group" clip-path="url(#clipPath_text)">
<rect class="shape_animation" fill="#F2385A" width="100%" height="100%"></rect>
<rect class="shape_animation" fill="#F5A503" width="80%" height="100%"></rect>
<rect class="shape_animation" fill="#E9F1DF" width="60%" height="100%"></rect>
<rect class="shape_animation" fill="#56D9CD" width="40%" height="100%"></rect>
<rect id="shape_animation_ref" class="shape_animation" fill="#3AA1BF" width="20%" height="100%" x="-25px">
</rect>
</g>
</symbol>
</defs>
<rect width="100%" height="100%" filter="url(#background_light)"></rect>
<use width="500px" height="100%" x="50%" xlink:href="#panel_animation" class="brandtype_use"></use>
</svg>
</div>
Thanks for any hint.
Text alignment in SVG does not work the way we are used to from HTML and CSS where everything is box with some dimensions and we can apply e.g. text-align: center.
In <svg:text> the starting coordinates define point from which will text line expand.
text-anchor attribute controls which direction this expansion will occur: center value means it will expand both ways so the initial anchor point will be in the middle of bounding box width (for horizontal writing systems). See excellent answer illustrating this text-anchor as the best mean for centering text in SVG: https://stackoverflow.com/a/23272367/540955. Also, there is no CSS position properties (left/top) inside SVG, only x/y coordinates, nor margins and rest of box-model as you know it in HTML.
So in your code adding text-anchor="middle" and moving the x coordinates further right would produce centered text. I'd advise to use bare <text> elements as opposed to <tspan>s, because shifting them with dx/dy is relative to the last preceding character and this character could be some white space from parent <text> (depending on code formatting) what would produce unbalanced centering. Also for easier calculations dominant-baseline="central" (or just middle for horizontal writing systems) is useful, because it moves the anchor point from the base line to "center line".
So using dy attribute (as you already do) to move the first line "one half" of line-height up and the other down should do the trick:
<svg viewBox="0 0 800 200" text-anchor="middle" dominant-baseline="central" font-size="100">
<!-- Outline and diagonals with center point for illustration: -->
<path d="M0,0h800v200h-800zl800,200m0 -200L0,200" fill="#FC9" stroke-width="1" stroke="rgba(0,0,0,0.3)"></path>
<circle cx="50%" cy="50%" r="10" fill="red"></circle>
<!-- Centered text: -->
<text x="50%" y="50%" fill="rgba(0,0,0,0.3)">So</text>
<!-- Shifted up and down: -->
<text x="50%" y="50%" dy="-0.5em">So</text>
<text x="50%" y="50%" dy="+0.5em">Food</text>
</svg>
(Not entirely related: the clipping could be done in CSS only with background-clip: text; here is rough variation of your design as it appears in Chrome browser, with animated text background, but without shadows. Unfortunately adding shadows would require more elements or attributes, I think. This should work in any browser supporting background-clip.)
div {
display: flex;
flex-direction: column;
align-items: center;
font-size: 30vh;
line-height: 30vh;
font-weight: bold;
font-family: Impact;
}
span {
color: #fff;
background-color: #000;
width: 100%;
text-align: center;
}
#supports (-webkit-text-fill-color: transparent) and (-webkit-background-clip: text) {
span {
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
animation: 2s wohoo infinite alternate cubic-bezier(1,0,1,1);
background-position: 0 0;
background-size: 100% 100%;
background-color: transparent;
background-image: linear-gradient(
to right,
#f2385a 0,
#f2385a 20%,
#f5a503 0,
#f5a503 40%,
#e9f1df 0,
#e9f1df 60%,
#56d9cd 0,
#56d9cd 80%,
#3aa1bf 0,
#3aa1bf 100%
);
background-repeat: no-repeat;
transform-origin: center center;
}
}
#keyframes wohoo {
from {
background-size: 0 100%;
background-position: -5vh 0;
transform: scale(0.7);
}
50% {
transform: scale(0.7);
}
90% {
transform: scale(0.9);
}
to {
background-size: 500% 100%;
background-position: 0vh 0;
transform: scale(0.9)
}
}
html,body{margin:0;overflow:hidden;}
body {
background-color: #1d1f20;
color: snow;
display: flex;
flex-direction: row;
justify-content: center;
width: 100%;
}
<div>
<span>Baz</span>
<span>Gazonk</span>
<span>Qux</span>
</div>

How to create a square shaped div inside an arbitrary circle?

I am using Javascript to create an SVG element that contains a circle with a radius and a stroke thickness. The size and thickness may vary. I'm trying to create a square shaped div that would fit inside this SVG circle, so that I may add content inside the circle.
You can imagine the content to be anything from a text containing information about the circle, an anchor, or a button.
The rectangle must fit in the circle in so that all content is wrapped, and if there is no space, the content will be removed.
Here is the raw Sketch
<!-- A minified example of what the Javascript outputs -->
<svg viewBox="0 0 80 80" width="80" height="80">
<circle cx="40" cy="40" r="35"></circle>
</svg>
My main question is if it's possible to add this solely to the SVG element, and using something like the styling: left: 10%; top: 10%; width:50%; height: 50%, or if this would require more advanced CSS or Javascript trickery.
It's important to also mention that my circle has a radius of (svgWidth / 2) * 0.875 that is set from within the Javascript code.
Okay, thanks to #Sergiu I found the right mathematical equation to solve it, this was the primary issue. The code below is taken out of my Javascript code and shows how I create a rect that fits exactly like the square my image.
var squareSize = Math.sqrt(2) * radius - circleStrokeThickness;
var squareCenter = (svgWidth - squareSize) / 2;
this.rectangleContent = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
this.rectangleContent.setAttribute('x', squareCenter);
this.rectangleContent.setAttribute('y', squareCenter);
this.rectangleContent.setAttribute('width', squareSize);
this.rectangleContent.setAttribute('height', squareSize);
this.rectangleContent = $(this.rectangleContent).appendTo(this.svg);
This is not a div but it already answers all of the questions I had about the placement of the div.
I believe this is what you are looking for. You can resize the SVG and see everything resizes accordingly.
.container {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
.container svg {
fill: #dedede;
stroke: #000;
width: 200px;
height: 200px;
overflow: visible;
background-color: lightblue;
border: 1px solid blue;
}
.container svg g > .text-holder {
background-color: lightcoral;
}
.container svg g > .text-holder > p {
font-size: 12px;
}
.container svg g > circle {
cx: 50%;
cy: 50%;
r: 50%;
}
.container svg g > rect {
stroke: #f00;
x: 15%;
y: 15%;
width: 70%;
height: 70%;
}
<div class="container">
<svg viewBox="0 0 80 80">
<g>
<circle></circle>
<rect></rect>
<foreignObject class="text-holder" x="15%" y="15%" width="70%" height="70%">
<p xmlns="http://www.w3.org/1999/xhtml" style="font-size: 12px;">Text goes here</p>
</foreignObject>
</g>
</svg>
</div>

How to diagonally divide a square div into 4 clickable parts?

This is how to diagonally "divide" a square into 4 parts:
div {
width: 0;
height: 0;
border-top: 50px solid red;
border-right: 50px solid blue;
border-bottom: 50px solid green;
border-left: 50px solid yellow;
}
<div>
</div>
(result)
I want to attach an event to each colored area - but of course you can't attach an event to a border. How should I do?
Here is one way to do it. Nested DIVs. I used wrapper with a a grid to lay them in the 2x2 pattern, then translated and rotated the wrapper. Clipped using the outermost div. Each one is easy to use the onclick for, and you don't need to do extra logic. Just a lot more effort to setup.
I've also got this set up so that you can easily change the size of the squares. However, this definitely won't work in IE, but I don't think we really need to worry about that.
Working codepen
document.querySelector(".red").onclick = () => alert("red");
document.querySelector(".blue").onclick = () => alert("blue");
document.querySelector(".yellow").onclick = () => alert("yellow");
document.querySelector(".green").onclick = () => alert("green");
.clip {
--size: 200px;
height: var(--size);
width: var(--size);
overflow-x: hidden;
overflow-y: hidden;
}
.rotate {
height: calc(var(--size) * 1.5);
width: calc(var(--size) * 1.5);
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
transform: translate(-50%, -50%) translate(calc(var(--size) * 0.5), calc(var(--size) * 0.5)) rotate(45deg) ;
}
.red {
background: red;
}
.blue {
background: blue;
}
.yellow {
background: yellow;
}
.green {
background: green;
}
<div class="clip">
<div class="rotate">
<div class="red"> </div>
<div class="blue"> </div>
<div class="yellow"> </div>
<div class="green"> </div>
</div>
</div>
If you want to pursue a single div/different logic depending on location, this might help.
I just wrote this in the console of this page:
document.body.addEventListener("click",(e)=>console.log(e))
And clicked, in this order, on these portions: "yellow", "red", "blue", "green" of your image. ( I held ctrl while clicking, so that it would open the image in new tab and this page would persist)
The resulting events (4 loggeed, ofc) had a path attribute that said on which element I clicked (in this case, principal one was img), and the offsetX & offsetY relative to this element
The relation was:
{
"yellow": {offsetX: 18, offsetY: 59},
"red": {offsetX: 59, offsetY: 25},
"blue": {offsetX: 85, offsetY 46},
"green": {offsetX: 61, offsetY: 78},
}
So yeah, you could use, from the MouseEvent:
offsetX
The offsetX read-only property of the MouseEvent interface provides the offset in the X coordinate of the mouse pointer between that event and the padding edge of the target node.
offsetY
The offsetY read-only property of the MouseEvent interface provides the offset in the Y coordinate of the mouse pointer between that event and the padding edge of the target node.
And determine where user clicked.
You can use clip path to make triangles and use some positioning to get them in the correct spots.
document.querySelector('.sq-tri').addEventListener("click", (evt) => {
console.log(evt.target.getAttribute('data-location'))
})
.sq-tri {
display: relative;
width: 5em;
height: 5em;
}
.tri {
position: absolute;
}
.tri-up,
.tri-down {
width: 5em;
height: 2.5em;
}
.tri-left,
.tri-right {
width: 2.5em;
height: 5em;
}
.tri:hover {
background-color: lime;
cursor: pointer;
}
.tri-up {
margin-top: 2.5em;
background-color: yellow;
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
}
.tri-down {
background-color: red;
clip-path: polygon(50% 100%, 0% 0%, 100% 0%);
}
.tri-left {
background-color: green;
margin-left: 2.5em;
clip-path: polygon(100% 100%, 100% 0, 0 50%);
}
.tri-right {
background-color: blue;
clip-path: polygon(0 100%, 0 0, 100% 50%);
}
<div class="sq-tri">
<div class="tri tri-down" data-location="top"></div>
<div class="tri tri-right" data-location="left"></div>
<div class="tri tri-left" data-location="right"></div>
<div class="tri tri-up" data-location="bottom"></div>
</div>
Use an SVG as detailed in this answer
Note: As each of the elements inside the SVG has an ID, you should be able to target them with JS/Jquery.
svg {
display: block;
width: 200px;
height: 200px;
margin: 25px auto;
border: 1px solid grey;
stroke: #006600;
}
#buttons polygon:hover {
fill: orange;
}
#top {
fill: #cc3333;
}
#right {
fill: #663399;
}
#left {
fill: #bada55;
}
<svg viewbox="0 0 100 100">
<g id="buttons">
<polygon id="top" points="0,0 100,0 50,50" />
<polygon id="right" points="100,0 50,50 75,75 100,100" />
<polygon id="bottom" points="0,100 50,50 75,75 100,100" />
<polygon id="left" points="0,0 25,25 50,50 0,100" />
</g>
</svg>

Categories

Resources