Mix-blend-mode: weird (temp) black background during SVG animation on android - android

I'm spinning a few rectangles atop each other, and their colors interact with using the css's mix-blend-mode.
In all browsers it's fine, but on chrome on Android there is a black background that is generated around the svg's rectangle shape while it's rotating. Once it settles into position, the black background disappears.
Pen: https://codepen.io/sashakevich/pen/YVMmZV
html:
<svg id="sl_logo" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 379.94 62.8"><g id="mark"><rect id="bar-1" class="cls-1" x="20.04" width="12.1" height="56" rx="3.24" ry="3.24"/><rect id="bar-2" class="cls-2" x="20.9" y="0.5" width="12.1" height="56" rx="3.24" ry="3.24" transform="matrix(0.5, 0.87, -0.87, 0.5, 37.29, -9.59)"/><rect id="bar-3" class="cls-3" x="20.9" y="0.5" width="12.1" height="56" rx="3.24" ry="3.24" transform="matrix(-0.5, 0.87, 0.87, 0.5, 15.5, -9.59)"/></g></svg>
css:
#bar-1, #bar-2, #bar-3 {
mix-blend-mode:multiply;
}
#bar-1 {
fill:#ed4237;
}
#bar-2 {
fill:#29aae2;
}
#bar-3 {
fill:#7ab642;
}
js
TweenMax.from("#bar-2", .35, {rotation:0, transformOrigin:"center center", delay:.4});
TweenMax.from("#bar-3", .7, {rotation:0, transformOrigin:"center center", delay:.4});
TweenMax.from("#letters", .7, {x:-40, opacity:0, transformOrigin:"left", delay:.4});
Any ideas how to get it to behave?

Ok, 2 hours later I have a solution....
Here's what didn't work in case you want to go down that road:
opacity: 0; fill-opacity:1;
enable-background: accumulate; on parent element
adding a white rectangle of 100% width and height
setting some large white stroke, setting no stroke, nor playing with stroke opacity.
What did work, was creating another blended element somewhere on the page. Not even interacting with the svg in any way FFS!
.blend-fix {
mix-blend-mode:multiply;
}
Working pen: https://codepen.io/sashakevich/pen/qmGBBL

Related

How to detect Android device's default font size with CSS media queries?

In Android, Settings > Accessibility > Font Size the user can set font size between "Small", "Default", "Large", "Largest". Among other things, this setting affects default font size of HTML content in WebViews.
I've developed my layouts to look well with the default font size. Setting font size to "Largest" causes text to get cut off in some places, a horizontal scrollbar appear on others etc. In these cases I can use an alternative layout (for example, stack things vertically instead of horizontally), but I'm not sure how to detect which layout to use.
Ideally, I would use CSS media queries. Something like:
#foo {
display: flex;
}
#media (min-width: 360px) {
#foo {
/* If at least 360px available, use a horizontal layout */
flex-direction: row;
}
}
Problem is, the 360px breakpoint isn't affected by the Font Size setting on the device (which makes sense). I've also tried other measurement units: rem, ch, cm – but none of them seemed to take into account device's font size.
I've thought about doing something like this on page load:
display a line of constant text (say, "0000000000") on the screen
measure it in JS
if the measured width / device width ratio is above some set constant, switch to the alternative layout
But this approach would add complexity, delay and repaints and flicker on page load.
Is there a way to take device font size in account in CSS (media queries or otherwise)?
Short Answer
No, you cannot do this just using CSS. However you can minimise impact using a method similar to the one you mentioned in your question (measuring font size and adjusting layout accordingly).
Long Answer
You cannot do this with just CSS, however it is possible to have a performant website without repaints and fall-back to your default styles for no JS.
There is one downside to this method, you do end up injecting a style sheet into the page which will affect first contentful paint times. However bear in mind that this is essentially the same as having a matching media query (so in reality, there is no difference between this and a media query other than it relies on JavaScript).
You can mitigate this by inlining the relevant styles but obviously that carries a page weight cost. You will have to decide which is the greater sin!
The solution is quite simple.
(1) Work out the user's font size using the method similar to the one you described.
(2) Load in conditional CSS that overrides the key layout options as you desire.
(2a) Alternatively add a class to the body and change the layout based on that from styles within existing style sheets or inlined in the document if above the fold.
1. Work out the user's font size
You can do this in vanilla JS right within the header of the page as an inline script so it does not delay anything (other than parsing the script) and it will still be performant.
Try the below example with you font size set to "medium" first, then set your font-size to "extra large" and run the script again. Your font size should show as 16px and 24px respectively.
var el = document.getElementById('foo');
var style = window.getComputedStyle(el, null).getPropertyValue('font-size');
var fontSize = style;
console.log(style)
<div id="foo">a</div>
We now have a font size that is relative to the user's scaling.
We can further improve this by simply dividing the resultant font size by 16 (default size) to get a %age scale.
var el = document.getElementById('foo');
var style = window.getComputedStyle(el, null).getPropertyValue('font-size');
var fontSize = style;
var fontSizePercentage = parseFloat(style) / 16 * 100;
console.log(style, fontSizePercentage + "%");
<div id="foo">a</div>
2. Load in conditional CSS
Now that we know if the font size has been scaled by the user we simply conditionally load CSS.
To do this we need a simple JavaScript check
//set to whatever criteria you need, if your site still works on "large" font size and only needs adjustment at "extra large" then use 124 etc.
if(fontSizePercentage > 100){
//add the CSS
}
In the below example I have 3 columns that turn into 3 rows to demonstrate how font size dictates which styles to apply.
Please note to simulate a dynamically added CSS file I added some inline CSS code that gets written to a style sheet, you would obviously just add a style sheet (I have included a function to do this and just commented out where that function is called).
var el = document.getElementById('foo');
var style = window.getComputedStyle(el, null).getPropertyValue('font-size');
var fontSize = style;
var fontSizePercentage = parseFloat(style) / 16 * 100;
el.remove();
console.log(fontSizePercentage);
//this is just to simulate adding CSS, you would obviously import a style sheet properly here. I have put the proper function further down.
var normal = `
.col3{
float: left;
width: 32%;
margin-right: 1%;
}
`
var bigger = `
.col3{
float: left;
width: 100%;
}
`
var styleSheet = document.createElement("style");
styleSheet.type = "text/css";
if(fontSizePercentage > 100){
styleSheet.innerText = bigger;
}else{
styleSheet.innerText = normal;
}
document.head.appendChild(styleSheet);
////actual style sheet code/////
function addCss(fileName) {
var head = document.head;
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = fileName;
head.appendChild(link);
}
if(fontSizePercentage > 100){
//addCss('url-to-large-font-size-layout');
}else{
//addCss('url-to-normal-font-size-layout');
}
<div id="foo">a</div>
<div class="col3">column</div>
<div class="col3">column</div>
<div class="col3">column</div>
You will see from the example we add the stylesheet dynamically, choosing between two style sheets in the example. In reality you would probably only need to do the check for the requirement to use the large fonts stylesheet as your standard font size will be covered by your main CSS.
pros
This method effectively is the same as a 'font size media query' and carries a tiny JavaScript overhead.
cons
If you are bothered by repaints then performance is obviously important to you, this method adds an additional request and can delay First Contentful Paint / Initial page rendering for "above the fold content".
Because of this I offer a second suggestion:
2a. Add a class to the body.
Use exactly the same method as above but instead of inserting a style sheet simply use the check for font size to add a class to the body on page load.
The simply include the styles within your current style sheets but with the additional body class as a qualifier.
var el = document.getElementById('foo');
var style = window.getComputedStyle(el, null).getPropertyValue('font-size');
var fontSize = style;
var fontSizePercentage = parseFloat(style) / 16 * 100;
el.remove();
var bod = document.getElementById('simulated-body');
if(fontSizePercentage > 100){
bod.classList.add('large-font-layout');
}
console.log(fontSizePercentage);
.col3{
float: left;
width: 32%;
margin-right: 1%;
}
.large-font-layout .col3{
float: left;
width: 100%;
}
<div id="simulated-body">
<div id="foo">a</div>
<div class="col3">column</div>
<div class="col3">column</div>
<div class="col3">column</div>
</div>
pros this doesn't add any extra requests, should not affect your page paint. Generally this option will be preferable to the first option as you should only need to override a handful of CSS classes so the added weight is negligible.
cons - adds extra weight to your CSS.
Conclusion
Somebody should add a "user font size" media query :-P
Seriously though I would go with the (2a) option I gave you and inline your critical CSS. If you are changing more than 100 classes in your CSS (and so CSS weight becomes a problem) then something is wrong with your design so the speed difference will be negligible. Couple that with the fact that the JS is less than 1kb and it will not affect your paints and it is a simple but effective solution to your problem.
Bonus info for option 2
As an additional thought you can combine option 2 with checking the screen width to really minimise the amount of data sent down the wire. However this then starts adding considerable complexity which is something you said you wanted to avoid. I have included it here for completeness.
function getFontSizePercentage(){
var el = document.getElementById('foo');
var style = window.getComputedStyle(el, null).getPropertyValue('font-size');
var fontSize = style;
var fontSizePercentage = parseFloat(style) / 16 * 100;
el.remove();
return fontSizePercentage;
}
function getPageWidth(){
return Math.max(
document.body.scrollWidth,
document.documentElement.scrollWidth,
document.body.offsetWidth,
document.documentElement.offsetWidth,
document.documentElement.clientWidth
);
}
function addCSS(fileName) {
var head = document.head;
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = fileName;
head.appendChild(link);
}
var fontSizePercentage = getFontSizePercentage();
var pageWidth = getPageWidth();
var cssSize = "1920";
switch(true) {
case (pageWidth < 1366):
cssSize = "1366";
break;
case (pageWidth < 728):
var cssSize = "728";
break;
default:
cssSize = "1920";
}
var cssPath = "styles/screen-width-" + cssSize + "-font-size-adjust";
if(fontSizePercentage > 100){
console.log("adding CSS for width: " + cssSize, cssPath);
addCSS(cssPath);
}else{
console.log("not adding CSS", cssPath);
}
<div id="foo">a</div>
you can use the VH (viewport height) or VW(viewport width) font-size measures and the font will resize based on % of device viewport for example:
1vh = 1% of viewport heigth
2vw = 2% of viewport width etc
If you are doing media selector you could as well use rem or px as font-size.
You will still have to add #media selector for different devices, keep in mind viewport is the actual size of a device screen, like 10inch screens will have a #media selector up to 1280px or bellow.
/* please use caniuse.com for browser compatibility table*/
h1 {
font-size: 2.5vw;
}
h2 {
font-size: 2vw;
}
p {
font-size: 12px;
}
#media only screen and (max-width:768px) {
h1 {
font-size: 5vw;
}
h2 {
font-size: 4vw;
}
p {
font-size: 18px;
}
}
<h1>text</h1>
<h2>text</h2>
<p>some normal font controled by media query</p>
Is there a way to take device font size in account in CSS (media queries or otherwise)?
Media queries are used to set font size based on device width, not the contrary.
The unit you might use will always be based on the device width, not on the font size (as this can't be already defined at this point).
What you can do is, instead of defining the width of your columns based on percentage or using px units, you can set them on the base font size.
For instance, instead of
.flex-col {
flex: 0 1 30%;
}
you may use
.flex-col {
flex: 0 1 20rem;
}
The real problem are not your media queries but the fact that your containers are not defined in a responsive way (with both fix width and height for instance) causing cut off and scrollbars.

Phantom double clicks from MouseArea on Android

While testing out an application on Android I noticed something funky going on. A double click event handler has been triggering without any double clicks occurring on that particular item.
Trying to isolate the issue I discovered that pretty much every chain of clicks rapid as a double click on regardless what two objects would cause the second click on the second object to register as a double click, when in fact it is just a single click.
Below is an example consisting of a row of 3 randomly colored rectangles, each one with a mouse area inside of it. The double click of each mouse area is rigged to set the parent rectangle's color to a different random color. Clicking rapidly two different rectangles under android triggers a double click and a color change for the second. This does not happen on Windows or Ubuntu Linux.
Window {
id: main
visible: true
width: 400
height: 400
title: qsTr("Hello World")
Row {
Rectangle {
width: main.width * .33
height: main.height
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
border.color: "black"
border.width: 2
MouseArea {
anchors.fill: parent
onDoubleClicked: parent.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
}
}
Rectangle {
width: main.width * .33
height: main.height
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
border.color: "black"
border.width: 2
MouseArea {
anchors.fill: parent
onDoubleClicked: parent.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
}
}
Rectangle {
width: main.width * .33
height: main.height
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
border.color: "black"
border.width: 2
MouseArea {
anchors.fill: parent
onDoubleClicked: parent.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
}
}
}
}
It looks as if the "previous click" or whatever property that's supposed to be used to detect double clicks is shared between different mouse areas instead of being per mouse area. The issue manifests in both Qt 5.7 and 5.7.1.
It definitely looks like my 10th discovered Qt bug this year, but I still feel like asking on the odd chance someone knows what's going on and how to fix it, because I need this fixed, and the Qt bugreport process is not speedy. So any ideas are more than welcome.
Until there is a better answer with an actual solution, it may be useful to know that it is possible to somewhat mitigate the devastating effect this issue has on user experience by reducing the global interval for double click detection.
By default it is the rather lethargic 500 msec. I found out that by reducing it to 250 msec helps to avoid over 90% of the incorrect double clicks:
QGuiApplication app(argc, argv);
app.styleHints()->setMouseDoubleClickInterval(250);
Additionally, there is a quick and hacky qml-only way to create a "fixed" copy of MouseArea:
// MArea.qml
Item {
id: main
property alias mouseX : ma.mouseX
property alias mouseY : ma.mouseY
property alias acceptedButtons: ma.acceptedButtons
// etc aliases
signal clicked(var mouse)
signal doubleClicked(var mouse)
// etc signals, function accessors
MouseArea {
id: ma
property real lClick : 0
anchors.fill: parent
onClicked: {
var nc = Date.now()
if ((nc - lClick) < 500) main.doubleClicked(mouse)
else main.clicked(mouse)
lClick = nc
}
}
}
This one actually works as intended and can be made almost entirely "plug and play" compatible with the original one.

jQuery wScratchPad: Responsive Design?

I have added the wScratchPad to a website project. And it's working.
But the project is based on responsive design. That means the size of all elements adapt to the screen size, so it's optimized for mobile devices too.
The situation is that the size of the scratch-area remains the same, no matter how small you make the browser-window.
<div id="wScratchPad"></div>
<script type="text/javascript">
var scratch = function(e, percent){
if ( percent > 50 ) {
sp.wScratchPad('clear');
}
}
var sp = $("#wScratchPad").wScratchPad({
width : 363,
height : 117,
realtimePercent : true,
scratchDown: scratch,
scratchMove: scratch,
cursor:'./cursors/coin.png',
scratchUp: scratch,
image: './images/1.png',
image2: './images/2.png'
});
</script>
The "width" and "height" are needed to calculate the overlaying canvas and the pixels for uncovering the background-image.
Is there any way to let the scratch area size adapt to the window-size?
One solution you could try would be to set the scratchpad's dimensions to be a percentage of the dimensions of the browser viewport, using document.documentElement.clientWidth/clientHeight. For example:
var sp = $("#wScratchPad").wScratchPad({
width : document.documentElement.clientWidth / 4,
height : document.documentElement.clientHeight / 4,
...
});

Android WebView HTML5 canvas error

I have an Android application which uses WebView to load the GUI using HTML. There is a <canvas> element into which i draw charts using Chart.js Javascript library for plotting charts.
It was tested on 3 devices. On two of them it works fine (Android 2.2 and 2.6), but on a later vesion of android (4.1.2) the canvas makes it double: all the charts are visible twice in the canvas, one of them is shifted a bit up and to the left.
What is the problem with the canvas? Why does it double the things rendered? How can i made it render only once?
Here's the code:
HTML:
<canvas id="graph_canvas"></canvas>
JavaScript:
var canvas=document.getElementById("graph_canvas");
canvas.height=200;
canvas.width=200;
var graphSelection=document.getElementById("graphSelection");
var ctx = canvas.getContext("2d");
var data_=JSON.parse(JI.getGraphData(graphSelection.value));
var myNewChart = new Chart(ctx).Line(data_);
Where graphSelection is a <select> element with which we select the chart,
JI.getGraphData returns JSON data for Chart.js.
If you do not want problems caused by "position: fixed;" on element,
canvas parent element should not have css property "overflow" with value "hidden".
So define something like "overflow: visible;" on parent element.
Try this:
// If Samsung android browser is detected
if (window.navigator && window.navigator.userAgent.indexOf('534.30') > 0) {
// Tweak the canvas opacity, causing it to redraw
$('canvas').css('opacity', '0.99');
// Set the canvas opacity back to normal after 5ms
setTimeout(function() {
$('canvas').css('opacity', '1');
}, 5);
}
I met this problem too. I solved it by using position:relative , left and -webkit-transform instead margin;. The problem was solved accidentally.

Get webpage center coordinates on tablet devices using JavaScript

I'm trying to compute the viewport geometric center on a webpage, rendered with Tablet devices (iOS and Android), ie the actual CENTER of the viewport (what you see) according to current translation and current zoom level - don't want the center of the document itself, I want the center of the screen what I'm viewing.
The problem is that this calculation is does not take into account any kind of zoom (an then) translation.
On iOS, I've tried with some of these answers, on the question detecting pinch to zoom on iOS,
I was able to catch the event "OnZoom" but didn't get any value, though.
On Android, I can't even catch any event related to zoom. I'm aware of touchmove and touchstart events, but how can I distinguish then in order to get zoom (and zoom value)
I'm using jQuery 1.7.2 library.
I have made a demo page which is confirmed to work on iPhone iPad, Samsung Galaxy Tab 10. I have only attached the click event on a huge div, so tap on the screen after zooming to update the display.
The calculation is very simple, use screen.width/window.innerWidth to get the zoom level. screen.width will always be in device pixels and window.innerWidth is always in css pixels, which also take the zoom into account.
Further calculation is simple math:
// round the result down to avoid "half pixels" for odd zoom levels
Math.floor(window.scrollY + window.innerHeight/2);
Math.floor(window.scrollX + window.innerWidth/2);
To check whether the user is zooming, attach the listener to window.resize and window.scroll which will fire after orientationchange hiding the address bar and zooming.
Here's my demo JavaScript:
var dot = document.getElementById("dot");
document.getElementById("main").addEventListener("click", function(e) {
var zoom = screen.width / window.innerWidth;
alert("zoom: " + zoom + "\n" + "ScrollY: " + window.scrollY);
dot.style.top = Math.floor(window.scrollY + window.innerHeight/2 - 5) + "px";
dot.style.left = Math.floor(window.scrollX + window.innerWidth/2 - 5) + "px";
}, false);
Setting up a simple HTML page, I can get pretty close to center.
With the code below, I'm just checking the width and height of a fixed div, and then combining that with the document offset and some simple maths to figure out the center, and place a single black dot there. I can get it pretty close, but it varies on my iPhone 4S.
Haven't tried on Android devices.
I don't think this would work on iOS <= 4, as they don't support fixed-positioning.
<style>
#fixed{
position: fixed;
left: 0; right: 0; top: 0; bottom: 0;
background: orange; opacity: 0.25;
}
.black{
position: absolute;
top:0;left:0;
width: 1px; height: 1px;
background: black;
}
</style>
<body>
<div id="fixed"></div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
<script>
jQuery(function($){
setInterval(function(){
var top = ($("#fixed").height()/2) + $("#fixed").offset().top;
var left = ($("#fixed").width()/2) + $("#fixed").offset().left;
$("body").append('<div class="black" style="top: '+top+'px; left: '+left+'px;" />');
}, 1500)
});
</script>
</body>
I had a client request a page zoom detection method in javascript a couple years ago.
In my case, he wanted it to work on a facebook app. Through the iframe canvas/viewport.
I used Max and Min functions
function getDocHeight() {
var D = document;
return Math.max(
Math.max(D.body.scrollHeight, D.documentElement.scrollHeight),
Math.max(D.body.offsetHeight, D.documentElement.offsetHeight),
Math.max(D.body.clientHeight, D.documentElement.clientHeight)
);
}
function getDocWidth() {
var D = document;
return Math.max(
Math.max(D.body.scrollWidth, D.documentElement.scrollWidth),
Math.max(D.body.offsetWidth, D.documentElement.offsetWidth),
Math.max(D.body.clientWidth, D.documentElement.clientWidth)
);
}
function getMinHeight(h) {
return Math.min(viewport.currentHeight, getDocHeight(), h);
}
getMinWidth was similar, but I had to apply browser-based tweaks to it.
I created an object called viewport which stored the properties of a fixed position div, specifically currentHeight and currentWidth were the offsetHeight and offsetWidth of the div element.
I ended up initializing a window.intervalTimer to run checks on the state of that div, comparedTo the stored values within the viewport object.

Categories

Resources