observables dependent on one another for their own values - circular references - javascript

Probably a knockout newbie question. I just started learning knockout this week.
I have two observables - width and height. These values are initially set by grabbing the width and height of a clicked element, so no calculation is needed for this part. The issue is that, once the initial values are captured, I want to maintain the aspect ratio for all future changes. So if the user updates the width, I want the height to update in accordance with the aspect ratio (original width / original height). So when a user changes the width, the height must change, and vice versa.
I have tried this using subscribers, but this causes an infinite loop as you might expect, as any change to the width requires a change in height and vice versa. I have looked into computed observables and extenders, but I am not sure either one is the right answer, though this may be due to my limited experience with knockout.
If necessary, I can provide some code, but thought I'd try to keep this conceptual if possible. Just something to point me down the right path.
Thanks for any help!

Like with any circular dependency, you have to break the cycle. Rather than having two mutually dependent observables, make them dependent on one or more common observables. Observables that can maintain that ratio.
e.g.,
function Proportional (width, height) {
var _ratio = width / height,
_width = ko.observable(width),
_height = ko.observable(height);
this.width = ko.dependentObservable({ read: _width, write: setWidth });
this.height = ko.dependentObservable({ read: _height, write: setHeight });
function setWidth(width) {
_width(width);
_height(width / _ratio);
}
function setHeight(height) {
_height(height);
_width(height * _ratio);
}
}
function ViewModel(data) {
var p = new Proportional(data.width, data.height);
this.width = p.width;
this.height = p.height;
}
fiddle

Related

Slow Scroll Toggle with no jQuery?

I know its a bit to ask, but is the following possible without using jQuery? I have it running with jQuery now but it seems to be presenting performance issues. If you could help I will be most grateful. I am not lazy, just not very code knowledgable. Took me a while to even get this far.
//
// default speed ist the lowest valid scroll speed.
//
var default_speed = 1;
//
// speed increments defines the increase/decrease of the acceleration
// between current scroll speed and data-scroll-speed
//
var speed_increment = 0.01;
//
// maximum scroll speed of the elements
//
var data_scroll_speed_a = 2; // #sloganenglish
var data_scroll_speed_b = 5; // #image-ul
//
//
//
var increase_speed, decrease_speed, target_speed, current_speed, speed_increments;
$(document).ready(function() {
$(window).on('load resize scroll', function() {
var WindowScrollTop = $(this).scrollTop(),
Div_one_top = $('#image-ul').offset().top,
Div_one_height = $('#image-ul').outerHeight(true),
Window_height = $(this).outerHeight(true);
if (WindowScrollTop + Window_height >= (Div_one_top + Div_one_height)) {
$('#sloganenglish').attr('data-scroll-speed', data_scroll_speed_a).attr('data-current-scroll-speed', default_speed).attr('data-speed-increments', data_scroll_speed_a * speed_increment);
$('#image-ul').attr('data-scroll-speed', data_scroll_speed_b).attr('data-current-scroll-speed', default_speed).attr('data-speed-increments', data_scroll_speed_b * speed_increment);
increase_speed = true;
decrease_speed = false;
} else {
$('#sloganenglish').attr('data-scroll-speed', '1').attr('data-current-scroll-speed', default_speed);
$('#image-ul').attr('data-scroll-speed', '1').attr('data-current-scroll-speed', default_speed);
decrease_speed = true;
increase_speed = false;
}
}).scroll();
});
I don't see any performance issue in your code, although there is space for some optimization. And I don't think jQuery might be the problem.
First thing to notice is the CSS access.
The height attribute is very expensive to access because it causes the browser to process many rendering steps of the pipeline, as you can see in CSS Triggers.
You are retrieving the height of two elements in a scroll event, which means that they will be calculated many times. Is it really necessary?
If your #image-ul element doesn't change its height, maybe you can calculate it outside of the event only once.
In the case of the window height, I believe it won't change in the scroll event. How about to create different handlers, one for the events that need to (re)calculate the window height and another for the events that don't need that calculation?
Another noticeable point is that you set the 'data-current-scroll-speed' and the 'data-speed-increments' attribute always with the same constant value. No change, no unset. Is it really necessary?
Actually, it is not clear what you are really doing. Your performance issue might be somewhere else.

Performance of CSS Flex vs. manual JavaScript computation

I have a browser-based system which consists of, among other modular components, an <iframe> container which is nested with other <iframe> for - currently - up to three levels. A given webpage may be embedded within multiple nested frames simultaneously. The end-users' screen resolutions and the nested frames' sizes can vary.
It is therefore important for element sizes, paddings, margins etc. to be defined in relative terms. To this end, I have identified two approaches: Either I use CSS Flex wherever possible and compute with JavaScript manually for the rest, or do the reverse and compute wherever possible. Here's an example of the computation-focused approach for one of my more complex pages to be embedded in the frames:
// Tile size-dependent CSS
const RATIO = 0.618;
// Amount of space to use in view
var viewHeight = window.innerHeight;
var viewWidth = window.innerWidth;
var viewVertSpace = viewHeight * 0.8;
var viewHoriSpace = viewWidth * 0.8;
// Position and sizing for each overall column
var colWidth = Math.round(viewHoriSpace * 0.5);
var colSpace = Math.round(viewVertSpace) - 2; // Deduct 2px bottom border
// Sizing of column 1 elements
var summaryHeight = colSpace * 0.5;
var mainRowHeight = summaryHeight * RATIO;
var mainRowSize = Math.round(mainRowHeight - 10); // Deduct 5px vertical padding per side
var subTextSize = Math.round((summaryHeight - mainRowHeight) * (1 - RATIO));
var diffIconSize = Math.round((mainRowSize - subTextSize) * RATIO);
// Sizing of column 2 elements
var horiSpace = colWidth * RATIO; // Leave some space on both sides
var chartWidth = horiSpace - (horiSpace * RATIO);
var innerBarWidth = chartWidth * (1 - RATIO);
var targetArrowWidth = subTextSize * 0.5;
There is a performance constraint on the system's loading time, one which has been failed during the first deployment to the test server. I have been continuously optimising the code (part of which involved implementing lazy initialisation and ordered loading to prevent too many simultaneous HTTP calls) and this is one area I'm looking at. I have read that extensive use of CSS Flex in more complex applications can have a significant performance impact but I wonder if relying on manual computation via JavaScript to set absolute pixel sizes is actually better?
While specific implementations may vary, here are some general things to consider:
You will not be able to control when the CSS causes your elements to resize, with JavaScript, you can make some decisions such as setting timeouts or establishing minimum values to trigger a change. However, any such solutions will be blocking any other JavaScript you may wish to be running in the same time frame. Similarly, any other JavaScript you have running will block this code. Using CSS Flexbox will require you to check on which browser-specific implementation details apply to your use cases (the same is of course true in your JavaScript).
In my experience, CSS flexbox has been faster than any JavaScript solutions that attempt to address the same concerns, I cannot guarantee that this is a universal truth though.
You should also consider code maintenance when implementing a solution. If your JavaScript is full of magic numbers and strange conditionals, it might be easier to maintain a CSS solution (assuming you do not fill it with magic numbers and strange conditionals as well, which I find easier to avoid with a Flexbox).
I'm sorry I can't give you a "use this every time answer", but hopefully this will help you make good decisions given the constrains that exist

multiple DIV collision detection in Javascript/JQuery

Working on a little "zombies" or "tag you're it" or "ew! you got cooties"-styled game where each AI object (a person, basically) runs around randomly. There is an initial object that is "it" or "infected" and as it moves about the screen and touches/overlaps/collides with another object it should change the touched object to the same color as the object that touched it. Newly infected objects can continue to infect other objects they randomly collide with, until - in principle - the whole population is the same color as the first infected object. (I'll worry about fancier AI where infected actively hunt nearby objects or healthy objects can avoid infected objects, later).
But after looking at various similar questions in StackOverflow that generally deal with 2 DIVs colliding, or use some sort of jQuery draggable detection trick, I'm still at a bit of a loss as to how to build upon those ideas to scale up a simple "if I am touching/overlapping/colliding with another object it should get infected too" that can be applied to a large number of elements on the page, say... less than 100 so as not to drag the browser down.
I basically get as far as determining position and widths/heights of the objects so that I know how much space they take, but then the brain goes 'bzzzzt' when trying to develop a function that checks over all the population for collisions.
Got the population moving around randomly without trouble - see JSFiddle https://jsfiddle.net/digitalmouse/5tvyjhjL/1/ for the related code. Affected function should be in the 'animateDiv()', seen below to make the stackoverflow question asking editor happy that I included some code in my question. :)
function animateDiv($target) {
var newq = makeNewPosition($target.parent());
var oldq = $target.offset();
var speed = calcSpeed([oldq.top, oldq.left], newq);
// I believe collision should be dealt with here,
// just before moving an object
$target.animate({
top: newq[0],
left: newq[1]
}, speed, function () {
animateDiv($target);
});
}
Any hints, tricks, adaptations, or code snippets that push me in the right direction are appreciated.
a quick, down and dirty solution (there are more complex algorithms) would be to use:
document.elementFromPoint(x, y);
It gets the element at the position specified. The full spec can be found here.
Assuming your 'zombies' are rectangular, you could call this for each corner, and if you get a hit, that isn't the background or the element you're checking, you've got a collision...
EDIT:
An alternate method, even 'downer and dirtier' than above, but stupidly quick, would be to get the centre points of the two objects to check, then find their absolute displacements in X and Y. If the differences are less than the sum of half their widths and heights then they are overlapping. It's by no means pix perfect, but it should be able to handle a large number objects really quickly.
EDIT 2:
First off, we need to get the centres of each object (to check)
// Values for main object
// pop these in vars as we'll need them again in a sec...
hw = object.style.width >> 1; // half width of object
hh = object.style.height >> 1; // (bit shift is faster than / 2)
cx = object.style.left + hw; // centre point in x
cy = object.style.top + hh; // and in y
// repeat for secondary object
If you don't know / store the width and height you can use:
object.getBoundingClientRect();
which returns a 'rect' object with the fields left, top, right and bottom.
Now we check proximity...
xDif = Math.abs(cx - cx1); // where cx1 is centre of object to check against
if(xDif > hw + hw1) return false; // there is no possibility of a collision!
// if we get here, there's a possible collision, so...
yDif = Math.abs(cy - cy1);
if(yDif > hh + hh1) return false; // no collision - bug out.
else {
// handle collision here...
}
Danny

EaselJS Triangle Stroke

I'm working with EaselJS to recreate something I've seen in real life and I'm having a slight issue with triangle strokes.
In the above image you can see my triangle. I understand corner A and why it isn't filled like the others but I want it filled. How can I do this exactly?
Because it won't include my code snippet, my JavaScript is:
var stage = new createjs.Stage('c'),
poly = new createjs.Shape(),
s = 400,
h = s * (Math.sqrt(3)/2),
x = stage.canvas.width/2+s,
y = stage.canvas.height/2+s/2;
poly.graphics.beginStroke('#0da4d3').setStrokeStyle(75)
.moveTo(x,y).lineTo(x+s/2,y+h).lineTo(x-s/2,y+h).lineTo(x,y);
stage.addChild(poly);
stage.update();
createjs.Ticker.addEventListener('tick', handleTick);
function handleTick(e) {
stage.update();
}
window.onresize = function() {
stage.canvas.width = $(window).width();
stage.canvas.height = $(window).height();
}
stage.canvas.width = $(window).width();
stage.canvas.height = $(window).height();
and a link to CodePen: http://codepen.io/Spedwards/pen/hqvsc
Also as a small sub-question, why is my stage only updating in a Ticker?
As kihu answered, you only need to add closePath to the graphics. Take a look at the documentation: http://www.createjs.com/Docs/EaselJS/classes/Graphics.html#method_closePath
For your sub question: the stage draw things on the screen on the stage.update() call. In your example, this call is inside a function executed every tick event, i.e., ~ 24 times per second. You only need to call stage.update when you have new things to draw (e.g., when you add other object to the stage or when you move, rotate or perform other transformations to the objects already in stage). Thus, in your case, you only need to call the update method after adding the shape to the stage and after the window resize event.
You can fix the corner issue using closePath();
poly.graphics.beginStroke('#0da4d3').setStrokeStyle(75);
poly.graphics.moveTo(x,y).lineTo(x+s/2,y+h).lineTo(x-s/2,y+h).closePath();
http://codepen.io/anon/pen/fyxvI
As for the ticker - this is how CreateJS was designed. I think it's related to game development. When animating things, you are 100% sure that all the operations inside 'tick' handler have been executed before the next 'tick' is handled.

Javascript stage scale calculator

I've searched far and wide throughout the web thinking that somebody may have had a similar need, but have come short. I'm needing to create a calculator that will adjust the size of a stage for draggable objects based on a Width and Height field (in feet).
I'm needing to maintain a max width and height that would, ideally, be set in a variable for easy modification. This max width and height would be set in pixels. I would set dimensions of the draggable items on the stage in "data-" attributes, I imagine. I'm not looking to match things up in terms of screen resolutions.
What's the best way to approach this? I'm pretty mediocre at math and have come up short in being able to create the functions necessary for scaling a stage of objects and their container like this.
I'm a skilled jQuery user, so if it makes sense to make use of jQuery in this, that'd be great. Thanks in advance.
There are at least a couple of ways to scale things proportionately. Since you will know the projected (room) dimensions and you should know at least one of the scaled dimensions (assuming you know the width of the stage), you can scale proportionately by objectLengthInFeet / roomWidthInFeet * stageWidthInPixels.
Assuming a stage width of 500 pixels for an example, once you know the room dimensions and the width of the stage:
var stageWidth = 500,
roomWidth = parseFloat($('#width').val(), 10) || 0, // default to 0 if input is empty or not parseable to number
roomHeight = parseFloat($('#height').val(), 10) || 0, // default to 0 if input is empty or not parseable to number
setRoomDimensions = function (e) {
roomWidth = parseFloat($('#width').val(), 10);
roomHeight = parseFloat($('#height').val(), 10);
},
feetToPixels = function feetToPixels(feet) {
var scaled = feet / roomWidth * stageWidth;
return scaled;
};
Here's a demo: http://jsfiddle.net/uQDnY/

Categories

Resources