Prevent subpixel rendering with svg - javascript

I'm working with SVGs currently and came to a dead end.
The SVG has lines, which should scale together with zooming (so that they stay in balance: 100% width 10px --> 10% width 1px for example)
i scale all stroke-widths with this code:
var svgPath = this._svgContainer.find('svg [class*="style"]');
for (var i = 0; i < svgPath.length; ++i) {
var newStrokeWidth = this._oldStrokeWidth[i] * (1 / (width / imgData.w));
$(svgPath[i]).css(
'stroke-width', newStrokeWidth
);
}
Where width is the new width after zoom and imgData.w is the original unscaled width.
The problem with this is, if i zoom in to far. The stroke with becomes to small and leads to sub-pixel rendering. And supposedly black lines get grey-ish.
My Idea was to clip the value at a certain point to prevent it.
But as far as I know, I have to consider the Device Pixel ratio too, because of different screens (desktop, mobile, 4K)
Would be nice If someone can help me with an idea to fix my problem

We finally found a solution for this, in case anyone has the same problems:
1) Because of the panning of this._$svgElement and the calculation of vpx in a completely different section of the code the element is 'between' pixels. ( 100.88945px for x for example). This causes lines to blur.
I fixed this part with a simple Math.round().
this._hammerCanvas.on('panmove', (event: any) => {
const translate3d = 'translate3d(' + Math.round(this._oldDeltaX + ((vpx === imgData.x) ? 0 : vpx) + event.deltaX) + 'px, ' + Math.round(this._oldDeltaY + ((vpy === imgData.y) ? 0 : vpy) + event.deltaY) + 'px, 0)';
this._$svgElement.css({
transform: translate3d
});
}
2) To fix the problem between the SVG viewport and the line strength, I had to implement a method to calculate the strokewidth equal to 1 'real' pixel regarding the svgs dimension.
the updated code looks like this: (This is the inital code, after the SVG was loaded from the server. Inside the zooming, the old code from above is still the same)
const pixelRatio = devicePixelRatio || 1;
const widthRatio = this._initSVGWidth / svgContainerWidth;
const heightRatio = this._initSVGHeight / svgContainerHeight;
this._svgZoomFactor = Math.max(widthRatio, heightRatio);
const strokeWidth1px = this.computeStrokeWidth1px(widthRatio, heightRatio);
for (let i = 0; i < svgPaths.length; ++i) {
this._initalStrokeWidth[i] = parseFloat($(svgPaths[i]).css('stroke-width'));
const newStrokeWidth = Math.max(strokeWidth1px / pixelRatio, this._svgZoomFactor * this._initalStrokeWidth[i]);
$(svgPaths[i])[0].setAttribute('style', 'stroke-width:' + newStrokeWidth);
this._oldStrokeWidth[i] = newStrokeWidth;
}
and the compute:
protected computeStrokeWidth1px (widthRatio: number, heightRatio: number): number {
const viewBox = this._$svgElement[0].getAttribute('viewBox').split(' ');
const viewBoxWidthRatio = parseFloat(viewBox[2]) / this._$svgElement.width();
const viewBoxHeightRatio = parseFloat(viewBox[3]) / this._$svgElement.height();
return widthRatio > heightRatio ? viewBoxWidthRatio : viewBoxHeightRatio;
}

var newStrokeWidth = this._oldStrokeWidth[i] * (1 / (width / imgData.w));
newStrokeWidth = (newStrokeWidth < 1) ? 1 : newStrokeWidth;
newStrokeWidth will always be 1 or greater

Related

Move browser with mouse

I am looking to try and do something like this where the content is off the screen and when you move the mouse the browser follows it around. I was thinking it would be similar to this where the edge of the screen animates when the mouse moves.
It looks like in the original example they use JS to change the transform: matrix. On the second link the screen is animated using greensock and the following code to change the CSS:
// Mouse move tilt effect
$(document).mousemove(function(event){
// Detect mouse position
var xPos = (event.clientX/$(window).width())-0.5;
var yPos = (event.clientY/$(window).height())-0.5;
// Tilt the hero container
TweenLite.to($hero, 0.6, {rotationY:5*xPos, rotationX:5*yPos, ease:Power1.easeOut, transformPerspective:900, transformOrigin:"center"});
// Update text on the page with the current mouse position
$(".bottom strong").text(event.pageX + ", " + event.pageY);
});
Is it possible to do something similar to do what I need?
Based on how I understood your intentions basically what you need to do is.
Create a div container which has width and height greater than window size and fill it up with content
Create div container which has width and height equal to window and overflow: hidden and contains the container in 1.
Center container in 1 in 2 with transform: translateX(-25%) translateX(-25%); and transition: transform 1s;
After that
Detected mouse position
Calculate distance from center of window
And based on that add or remove up to 25% to the translateX and translateY value
EDIT:
document.querySelector('body').style.transition = 'transform 1s';
window.addEventListener('mousemove', event => {
const absMaxX = window.innerWidth / 2;
const absMaxY = window.innerHeight / 2;
const maxDistance = Math.sqrt(Math.pow(absMaxX, 2) + Math.pow(absMaxY, 2));
const mouseX = event.clientX;
const mouseY = event.clientY;
const directionX = mouseX - absMaxX >= 0 ? 1 : -1;
const directionY = mouseY - absMaxY >= 0 ? 1 : -1;
const distance = Math.sqrt(Math.pow(mouseX - absMaxX, 2) + Math.pow(mouseY - absMaxY, 2))
const translation = distance / maxDistance * 100;
document.querySelector('body').style.transform =
`translateX(${directionX * translation}px) translateY(${directionY * translation}px)`
});

Filling in the screen with a diamond grid with pure javascript (jsfiddle is available)

Those who solve this will get 150 reputation points once im eligible for a bounty.
https://jsfiddle.net/testopia/xzxe6y5k/
As you can see in the jsfiddle I did some trigonometric calculations to figure out the exact position for the adjacent placements.
The following formula gives me the exact positioning:
elem.offsetHeight * Math.cos(degrees converted into radians) + elem.offsetTop
elem.offsetWidth * Math.cos(degrees converted into radians) + elem.offsetLeft
Of course the same thing is also possible by getting the vertex points, the code would just be larger. Here a small example:
elem.offsetLeft + elem.offsetWidth
elem.offsetTop + elem.offsetHeight
Anyways, I figure that automatic placement is pretty hard. I mean I am trying to achieve something like in the image below: http://www.purplesquirrels.com.au/wp-content/uploads/2015/02/dg.png
Question: So how can I make the diamond grid spread to the full height and width of the screen / container from the center? Not a loop from left to right and top to bottom but starting from the center in a somewhat circular way.
I was able to get the screen filled with two while loops. For now I used some static margins, so the spacings are not perfect, but I guess your computePosition function can help with generating the right spacings between the diamonds.
https://jsfiddle.net/xzxe6y5k/3/
var wrapper = document.getElementById('grid'), diamond = wrapper.children, newDiamond, prevDiamond, evenRow = true;
function createDiamonds() {
while (!newDiamond || newDiamond.getBoundingClientRect().bottom < window.innerHeight) {
evenRow = !evenRow;
prevDiamond = newDiamond;
newDiamond = wrapper.appendChild(document.createElement('div'));
if (prevDiamond) {
newDiamond.style.top = prevDiamond.getBoundingClientRect().bottom + 10 - (newDiamond.getBoundingClientRect().height / 2) + 'px';
if (evenRow) {
newDiamond.style.left = diamond[0].getBoundingClientRect().left + newDiamond.getBoundingClientRect().width / 2 + 7 + 'px';
}
}
while (newDiamond.getBoundingClientRect().right < window.innerWidth) {
prevDiamond = newDiamond;
newDiamond = wrapper.appendChild(document.createElement('div'));
newDiamond.style.left = prevDiamond.getBoundingClientRect().right + 10 + 'px';
newDiamond.style.top = prevDiamond.style.top;
}
}
}
createDiamonds();
#grid div {
background: black;
height: 25px;
width: 25px;
position: absolute;
transform: rotate(45deg)
}
<div id="grid"></div>

Using OpenLayers ZoomBox in JavaFX WebView

I am using JavaFX WebView included in jdk-8u45 to open a web page which shows a map using OpenLayers 2.13.1. I'm trying to zoom in on the map using a ZoomBox with a BoxHandler. The zooming works like it should, but the problem is how the the rectangle is drawn.
The wanted result is that once I click on the map and start dragging, the rectangle should start drawing as I move the mouse. This works fine in all browsers, except inside my WebView. What happens is that only after I have moved my mouse a few cm in both x- and y-direction (e.g. diagonally), the rectangle starts drawing from this position (not the one where I started dragging). I have looked at the coordinates from the different mouse events, and they all seem to be correct, which is confirmed by the fact that it zooms in on the area I actually dragged over (e.g. not the area that is drawn).
JavaScript console.log stmts output coordinates from the moment I click on the map, but nothing is drawn in the beginning of the drag.
Has anyone had similar problems with WebView? As I said, the code works like a charm in all other browsers I have tried (Safari, Firefox, Chrome, IE). I have looked around on the internet, but I haven't been able to find an answer to my problem.
Code taken from BoxHandler.js:
startBox: function (xy) {
;;;console.log(xy);
;;;console.log("startbox xy=" + xy.x + "," + xy.y);
if(this.zoomBox){
this.removeBox();
}
this.zoomBox = OpenLayers.Util.createDiv('zoomBox',
this.dragHandler.start);
this.zoomBox.className = this.boxDivClassName;
this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
this.map.viewPortDiv.appendChild(this.zoomBox);
OpenLayers.Element.addClass(
this.map.viewPortDiv, "olDrawBox"
);
},
/**
* Method: moveBox
*/
moveBox: function (xy) {
var startX = this.dragHandler.start.x;
var startY = this.dragHandler.start.y;
;;;console.log("dragHandler.start.x=" + this.dragHandler.start.x);
;;;console.log("dragHandler.start.y=" + this.dragHandler.start.y);
var deltaX = Math.abs(startX - xy.x);
var deltaY = Math.abs(startY - xy.y);
this.zoomBox.style.width = Math.max(1, deltaX) + "px";
this.zoomBox.style.height = Math.max(1, deltaY) + "px";
this.zoomBox.style.left = xy.x < startX ? xy.x+"px" : startX+"px";
this.zoomBox.style.top = xy.y < startY ? xy.y+"px" : startY+"px";
console.log("zoombox width=" + this.zoomBox.style.width);
console.log("zoombox height=" + this.zoomBox.style.height);
console.log("zoombox left=" + this.zoomBox.style.left);
console.log("zoombox top=" + this.zoomBox.style.top);
// depending on the box model, modify width and height to take borders
// of the box into account
var box = this.getBoxCharacteristics();
;;;console.log("movebox xOffset=" + box.xOffset);
;;;console.log("movebox yOffset=" + box.yOffset);
if (box.newBoxModel) {
if (xy.x > startX) {
this.zoomBox.style.width =
Math.max(1, deltaX - box.xOffset) + "px";
}
if (xy.y > startY) {
this.zoomBox.style.height =
Math.max(1, deltaY - box.yOffset) + "px";
}
}
},
/**
* Method: endBox
*/
endBox: function(end) {
var result;
console.log(this.map.viewPortDiv.lastElementChild.style);
if (Math.abs(this.dragHandler.start.x - end.x) > 5 ||
Math.abs(this.dragHandler.start.y - end.y) > 5) {
var start = this.dragHandler.start;
var top = Math.min(start.y, end.y);
var bottom = Math.max(start.y, end.y);
var left = Math.min(start.x, end.x);
var right = Math.max(start.x, end.x);
result = new OpenLayers.Bounds(left, bottom, right, top);
} else {
result = this.dragHandler.start.clone(); // i.e. OL.Pixel
}
this.removeBox();
this.callback("done", [result]);
}
Also, I don't know if this is relevant, but when I inspect the HTML div element that holds the map (using Firebug Lite) it looks like the top border of the div is further down than it should be. The map (in correct position on the webpage) is extending beyond the top border. This is different behavior than in the other browsers I mentioned.
Any help would be appreciated.

Limiting an element from appearing out of the screen

I'm trying to make a square appear at random positions of the screen. I have set it's position property to be absolute and in javascript i'm running a random number between 0 to 100, this will then be assigned as a percentage to top and left property. however if the random number was ~100 or a bit less the square will appear out of the screen. How do I fix this problem?
var shape1 = document.getElementById("shape1");
//creating random number to 100
function randomNum() {
var r = Math.random();
var y = (r * (100 - 0 + 1)) + 0;
var x = Math.floor(y);
console.log(x);
return x;
}
//reappear at random position
function reappear(object) {
object.style.left = randomNum().toString() + "%";
object.style.top = randomNum().toString() + "%";
object.style.display = "block";
}
reappear(shape1);
code: https://jsfiddle.net/y3m4ygow/1/
You can call the getBoundingClientRect method (MDN reference) on the object and check to see if its bottom property is bigger than window.innerHeight (means it's falling outside the window height), or if its right property is bigger than window.innerWidth (means it's falling outside the window width), and if so, call the reappear function again:
function reappear(object) {
object.style.left = randomNum().toString() + "%";
object.style.top = randomNum().toString() + "%";
object.style.display = "block";
var rect = object.getBoundingClientRect();
if(rect.right > window.innerWidth || rect.bottom > window.innerHeight)
reappear(object);
}
Fiddle update: https://jsfiddle.net/y3m4ygow/2/
As you can see what's happening here is sometimes the object falling out of the document because (the width or height of it + the randomized percentage) is more than document width or height.
For example, say that document width is 1000px and the random number turned out to be 90% (=900px), since the box width is 200px, so you will have 100px out of the screen.
Now you have two solutions:
First: As #Sidd noted, you can check whether the box is in or out using getBoundingClientRect this will return a variable for you having two properties one is bottom which is the distance of the box from the upper edge of the document, the other property is height which is the distance of the box from the left border of the screen.
Now what you can do is compare those two values with the document width and height.
Now by adding those three lines you'll have your problem solved:
var rect = object.getBoundingClientRect(); // getting the properties
if(rect.right > window.innerWidth // comparing width
|| rect.bottom > window.innerHeight) // comparing height
{reappear(object);} // re-randomizing
https://jsfiddle.net/y3m4ygow/2/
this WILL work, but it might produce some flickering with some browsers, and i'm not very comfortable about calling a function inside itself.
Second Solution is: which is what I would prefer you to do, is by not using a percentage, but using a fixed height and width values.
you can get the current height and weight values from the window object and substract your box dimensions from it:
var cHeight = window.innerHeight - 200;
var cWidth = window.innerWidth - 200;
set those two as the maximum value for the top and the right.
function topRandomNum() {
var r = Math.random();
var y = (r * (cHeight - 0 + 1)) + 0;
var x = Math.floor(y);
return x;
}
function rightRandomNum() {
var r = Math.random();
var y = (r * (cWidth - 0 + 1)) + 0;
var x = Math.floor(y);
return x;
}
and here's the fiddle for the second solution: https://jsfiddle.net/uL24u0e4/

Adjust position of a group of adjacent divs in non-linear fashion

This isn't so much a jQuery question as it is an overall conceptual question.
In my example I can populate a container with divs that have a top value set in a nonlinear fashion.
The top value of each one is calculated based on a formula that takes into account the top position of the one to its left as well as the height of the container (line 33 of fiddle).
//this formula sets the top value for each new child added to the container
//height is 100% of its parent which is 20% of the body
//newOne:last is the most recently added child and will have an initial top value of 10%
parseInt($(this).next().css('top'), 10) / $('#queue').height()) * 75 + (parseInt($('.newOne:last').css('top'), 10) * 2) + '%'
I more of less stumbled upon this by chance and it seems to work 'ok', but if an optimization is obvious to you, please point it out :)
What I'm having trouble coming up with is an elegant formula for how to adjust the children smoothly during a drag event. I'm thinking the top value needs to be adjusted based on some manipulation of the left offset, but after hours of experimenting, I haven't found anything that keeps the original position intact when I start dragging and continues adjusting the values smoothly during my drag. The children should gradually approach a minimum top value of 10% as I drag left (child with left offset of 0 will have a top value of 10%), and gradually move away from that top value back toward their initial position as I drag right.
$('#queue').draggable({
axis: "x",
scroll: false,
drag: function(){
//adjust values of each child
$('.newOne').each(function(){
var percentLeft = $(this).offset().left / $('footer').width() * 100
var thisLeft = parseInt($(this).css('left'), 10) / $(window).width() * 100;
var thisTop = parseInt($(this).css('top'), 10) / $('#queue').height() * 100;
if (percentLeft >= 0){
//top value of each one gradually decreases...
//as it gets closer to an offset value of 0 and minimum top value of 10%
//non-linear attempt but not even close
//$(this).css('top', $(this).css('top', 10 + (thisTop - 10 / thisLeft) + '%'));
//linear step
$(this).css({'top': 8 + (percentLeft/2) + '%'});
}
});
}
});
http://jsfiddle.net/5RRCS/17/
P.S. I know I'm asking a lot here, but hopefully someone is up to the challenge :)
Update:
Stumbled onto exp method and did something like this:
adjustTop = function(offset){
return 100 * (1.0-Math.min(0.98,(0.83 + ( 0.17/ (Math.exp(0.007*offset))) )) ) + '%';
};
$(this).css('top', adjustTop($(this).offset().left) );
Here's a version that I believe does what you are looking for.
The first thing I did was to refactor the top calculation so that both the initialization and the drag handlers would get the same results.
Rather than calculate the positions of the child divs based on their offset to the document, I changed the logic to use position relative to their container.
I also remove z-index as the child divs already being added the parent with the correct stacking order - the left most child is the last element in the container.
Calculating the height of each child depended on whether #queue's current position was to the left or right of its origin.
I also change the iteration logic to behave the same to simplify calculating the current elements starting offset:
$($('.newOne').get().reverse()).each(function (index) {
$(this).css({
'background': 'rgba(255,255,255,.80)',
'top': calcTop($(this), index)
});
});
Code for positioning the child elements:
function calcTop($ele, index) {
var elePositionLeft = $ele.position().left;
var queuePositionLeft = $('#queue').position().left;
var footerWidth = $('footer').width();
var queueHeight = $('#queue').height();
var distanceToTravel = queuePositionLeft < 0 ? elePositionLeft : footerWidth - elePositionLeft;
var percentTraveled = Math.abs(queuePositionLeft) / distanceToTravel;
var thisPercentLeft = (elePositionLeft + queuePositionLeft) / footerWidth;
var queuePercentLeft = queuePositionLeft / footerWidth;
var newTop;
var myStartOffset = (index + 1) * startOffset;
var topTravel = queuePositionLeft < 0 ? -myStartOffset + startOffset : (queueHeight - startOffset);
var linear = false;
if (linear) {
newTop = myStartOffset + (topTravel * percentTraveled);
newTop = newTop > startOffset ? Math.round(newTop) : startOffset;
return newTop;
} else {
if (queuePositionLeft >= 0) {
newTop = myStartOffset + (topTravel * thisPercentLeft * percentTraveled);
newTop = newTop > startOffset ? Math.round(newTop) : startOffset;
} else {
newTop = myStartOffset + (topTravel * (1+thisPercentLeft) * percentTraveled);
newTop = newTop < startOffset ? startOffset : Math.round(newTop);
}
return newTop;
}
}
There was also a minor bug in the reset function - it wasn't setting childCount back to zero:
$('#reset').click(function () {
$('#queue').empty().css('left', 0);
childCount = 0;
});
Demo Fiddle

Categories

Resources