Parallax like with Angular onscroll event - css

I tried to create a sort of parallax with Angular and OnScroll event, however when scrolling the text that appears is blinking. Do you know how to make the effect rendering smoother onscroll ? Maybe with CSS only ?
The script created
https://stackblitz.com/edit/angular-b5cmf7?file=src%2Fapp%2Fapp.component.html
The main function that make the effect possible is this one
#HostListener('window:scroll', ['$event'])
isScrolledIntoView(){
const offsetHeight = this.testDiv.nativeElement.children[0].offsetHeight;
const rect = this.testDiv.nativeElement.getBoundingClientRect();
if( rect.bottom <= window.innerHeight){ // when the div is visible in viewport change the height (not more than the height of the content)
let height = (window.innerHeight - rect.bottom);
if(height<offsetHeight){
this.height = height;
}else{
this.height = offsetHeight
}
}
}

As soon as I saw your blitz I recognised an issue I've run into many times. It's to do with how you calculate values on a per-frame basis. I'm not sure how best to put this in words, but it's something like this:
Don't base your calculations on values that will be modified by the result.
It's a bit abstract, but specifically in your case, the problem is with the relationship between rect.bot and this.height. With each call of your onscroll function, you modify the height of your section. But modifying the height will also modify the value of rect.bot, and on the next scroll event, you'll get a different result, so height gets modified from a different base, resulting in the oscillation you observed.
To avoid this, you want to base all your calculations only on values that are not affected by the result. In this case, I would recommend using rect.top, which doesn't care what the height of the section is.
Furthermore, I'd recommend using interpolation, because it gives you more control. The idea is you want to pick these values:
at what percentage of the window height should the reveal commence (effectively, this.height = 0)
At what percentage of the window height should the section be fully revealed (this.height = offsetHeight)
How should I interpolate between these two values? (easiest is linear)
So I've forked your blitz, and I've gone ahead and outright stolen the clamp and invlerp functions from here:
https://www.trysmudford.com/blog/linear-interpolation-functions/
You can read about these interpolation functions and their uses in that link.
you'll see I've made two variables called "startPercentage" and "endPercentage" – these are in relation to the value of rect.top as a percentage of window innerHeight. You can play around with these to get different speeds or qualities of parallax to get the effect of your liking.
Here's the fork:
https://stackblitz.com/edit/angular-lcyc4q?file=src%2Fapp%2Fapp.component.ts
And if you feel like it, look into other interpolation functions to experiment with ease-in and ease-out for different effects.

Related

How to clip areas drawn by `:layout` methods in awesome-wm?

This is a follow-up to How to make mouse events propagate to widgets in `scroll` containers?
TL;DR:
The :layout method I implemented makes the widget draw all over the other widgets (see pictures below). How can I constrain where the :layout method will draw my widgets and where it will allow me to interact with the children?
Longer version:
So I ended up patching the scroll container and what I basically did was that I implemented a :layout method, based on the offset calculations that were already made in the original scroll container code.
This is basically what I did (I'll only put the relevant parts here):
-- this function emits signals `self._private.fps` times a second
local _need_scroll_redraw = function(self)
if not self._private.paused and not self._private.scroll_timer then
self._private.scroll_timer = timer.start_new(1 / self._private.fps, function()
self._private.scroll_timer = nil
self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed") -- this is the only
-- line that I added
-- to this function
end)
end
end
local function calculate_info(self, context, width, height)
-- this is a longer function, but in summary here we calculate the
-- ideal size that the child would like to be, we see if the child
-- is bigger than the space we have for drawing, and if it is,
-- we calculate offsets (and we call `_need_scroll_redraw` here)
-- so we later know where and how often to `:draw` and `:fit` it
end
function scroll:fit(context, width, height)
local info = calculate_info(self, context, width, height)
return info.fit_width, info.fit_height
end
function scroll:layout(context, width, height)
local result = {}
local i = calculate_info(self, context, width, height)
table.insert(result, base.place_widget_at(
self._private.widget, i.first_x, i.first_y, i.surface_width, i.surface_height
))
return result
end
-- Also, there was a `:draw` method, but I took it out entirely since
-- if I add the `:layout` method, things get drawn just fine
-- P.S: I also tried to implement what was in the `:draw` method, inside
-- the `:layout` method, so that it'll clip properly. I also tried that idea
-- with the `:before_draw_children` and `:after_draw_children` methods
-- but since I don't know how to use cairo, god knows what I wrote there,
-- but it didn't work
With the default scroll widget, my widget looks like this, but nothing I click on works:
But with the changes I made above, the widget with the rows does scroll, and I can click on each child just fine and have it react, it's just that it draws everything outside of its boundaries, and I can also click on stuff that's outside of the boundaries:
So my question is: how would I go about restricting what the :layout method displays, to have it work the way the default scroll layout works, but still be able to interact with the children?
Untested, but I guess:
function scroll:before_draw_children(context, cr, width, height)
cr:rectangle(0, 0, width, height)
cr:clip()
end
This takes the rectangle describing the size of the scroll widget and clips all children to it, meaning that they cannot draw outside of the scroll widget.
Note that this is only prevents the drawing of the child widgets. It does not make awesomeWM "forget about them". What I mean with that is: You can still click on the where the widget would be if it were visible. The click will still be handled by the widget. Only the actual drawing of the child widget is prevented.
The above is why I asked in your original question whether you want just this one widget to be scrolled, without anything next to it. Because if the scroll widget covers the whole wibox, this whole class of issues just goes away.
And no, as far as I know / see, there is no way to prevent the not-drawn widgets from receiving button clicks.

Trying to zoom image based on mouse origin, yet my math is slightly off

I'm working on a full screen image viewer, I'll temporarily open a dev URL here:
http://www.jungledragon.org/apps/jd3/image/704/great_grey_owl.html/zoom
This viewer is responsive and scales to your browser width/height. One of its key features is being able to zoom in and out of the image using your mouse wheel. Rather than a center-based zoom, the idea is to zoom based on origin, meaning the coordinates of your mouse, allowing you to zoom into specific areas of the image.
How to reproduce the issue
If you open the above URL and have a quick play with your mouse wheel, it may appear to be working correctly. However, the math I am using is slightly off. Here is how you can reproduce the issue:
Open the above URL
Hover your mouse over the left eye of the Owl
Zoom one step using your mouse wheel, it should zoom exactly into the eye
Position your mouse on the owl's beak
Zoom one more step using your mouse wheel
You should now notice that the second zoom step did not go into the Owl's beak exactly, it seems to be slightly off, both horizontally and vertically. I'm thinking this is a result of bad math.
How it works
Here is the javascript that handles it all:
http://www.jungledragon.org/apps/jd3/js/jd3-slideshow.js
I am capturing the mousewheel event. Based upon its direction, I am increasing or decreasing the zoom level. The actual zooming is nothing more than applying a CSS class that scales the image using a CSS3 transform:
&.grow1 { #include jd-scale(1); }
&.grow2 { #include jd-scale(1.5); }
&.grow3 { #include jd-scale(2.0); }
&.grow4 { #include jd-scale(2.5); }
&.grow5 { #include jd-scale(3.0); }
Note: the above is a call to a SASS mixin that translates into the right vendor prefixes for transform:scale.
The above accomplishes the basic zooming without issues. To make origin-based zooming possible, however, a few more steps are needed. Upon doing the actual zooming, I first set the origin of the zoom in javascript, using transform-origin. Here is my helper function for setting it:
function zoomOrigin(selector, originStr) {
selector.css({'-webkit-transform-origin': originStr});
selector.css({'-moz-transform-origin': originStr});
selector.css({'-ms-transform-origin': originStr});
selector.css({'-o-transform-origin': originStr});
selector.css({'transform-origin': originStr});
}
The heart of this question is about calculating the correct origin. There are two things worthy to mention in calculating this value:
The absolute coordinates (meaning the X and Y) are relative to the image, not relative to the page
The calculation of the origin should take into account that the image has grown/shrunk based on the current zoom state
The origin calculation happens in realtime, based on the mousemove event. Here is the method that does so, with irrelevant parts removed:
$("#image-container img").mousemove(function(e) {
// user has moved their mouse. in case of zooming or panning, this means that the
// origin (center point) of those interactions need to be recalculated
// calculate the mouse offset within the zoomable object (which is different than the page-level offset)
// this relies on the parent of the element having position:relative set
var parentOffset = $(this).offset();
zoomOriginX = e.pageX - parentOffset.left;
zoomOriginY = e.pageY - parentOffset.top;
// recalculate the width and height of the image given the current zoom level
width = $(this).outerWidth() + (1 + ((zoomLevelCurrent - 1)*0.5) * $(this).outerWidth());
height = $(this).outerHeight() + (1 + ((zoomLevelCurrent - 1)*0.5) * $(this).outerHeight());
// calculate origin percentages based on zoomed width and height
// the zoom methods rely on these variables to be set
zoomOriginPercX = (zoomOriginX / width * 100);
zoomOriginPercY = (zoomOriginY / height * 100);
});
The main purpose of this method is to correctly set the global variables zoomOriginPercX and zoomOriginPercY, which are used to set the origin (percentage) prior to zooming.
From a math perspective, my idea was to simply calculate the zoomed width of the image, and to use the offset X and Y to come to a reliable origin percentage. As the problem statement shows, I am quite close to a correct calculation, yet something is off.
Although the zooming currently works well, I want it to be perfect. It would make for quite a powerful image viewer that is really easy to implement, also for others.
Desired Effect
To start answering your question I think it's worth first clarifying the desired effect. Essentially you're looking for the same effect you'd get if you pinched to zoom on an iPhone - the 'origin' of the pinch stays exactly the same, and everything around it stretches. You can imagine pinning some stretchy fabric at the origin, and pulling the corners.
Problem
This is working fine for you if you don't move the mouse between zooms, but if you do, the origin appears to move. The cause of the problem is exactly that - you are changing the origin of the transform every time you move the mouse. Of course you do need to do this, but you are calculating the origin based on the original (100% zoomed) position of the image. The actual origin needs to be somewhere between the origin of the first zoom and the new mouse position.
In other words, CSS is just doing one transform. If you set the origin to x,y then zoom to zoom level 2, this will give the same result as if you set the origin to x2,y2, zoom to level 1, then move to x,y, and go to level 2.
Solutions
I presume you could solve the issue in several ways:
Calculate a scaling factor for the 'new' origin on each zoom
this is likely a function of zoom level, mouse position and previous origin
Calculate and apply a translation each time the origin is moved
again will depend on the current origin, zoom level and mouse position
Find another way to 'stack' transforms on top of one another.
One way to do this may be to dynamically generate a new containing div each time you and apply a scale transform to that similar to the accepted solution in this question.
Unfortunately I don't have the time to go further than this, but hopefully it points you in the right direction?

Different x background-positions for rows in a tiled background

I want to create a tiled background in which each row of the background has a random x-offset. That would be ideal, but if that's not possible with CSS (I think not), at least what's the best way to use a single image to create a body background in which this several (let's say 4) rows of this image have different x positions?
Something like:
IMAGEIMAGEIMAGEIMAGE
MAGEIMAGEIMAGEIMAGEI
AGEIMAGEIMAGEIMAGEIM
GEIMAGEIMAGEIMAGEIMA
IMAGEIMAGEIMAGEIMAGE
MAGEIMAGEIMAGEIMAGEI
AGEIMAGEIMAGEIMAGEIM
GEIMAGEIMAGEIMAGEIMA
what would be the size of your image? If is is very small, an easy solution would be to do that in photoshop/paint. The resulting image would be 4x larger, but for small images (read less than 25ko), I think it is by far the easiest way. At least I don't see any easy CSS solution to your problem.
If your picture is quite big and you know its height, you could eventually have the following approach: http://jsfiddle.net/7GPTy/
The idea is to create several divs at z-index:-1 of height == image_height and absolute position with different left values... i'd see that as a coarse but working solution
var mybody = document.getElementById('body');
var bodyHeight = mybody.offsetHeight;
var imgHeight = 49; // you could get the size by opening the file
var offsetLines = 4; // this is the number of offset you asked, but you can change that!
var nbOfLines = Math.ceil(bodyHeight/imgHeight);
for(var i=0; i<nbOfLines; i++){
var newBgDiv = document.createElement('div');
newBgDiv.className = 'backgroundImg imgOffset_'+(i%offsetLines);
newBgDiv.style.top = (i*imgHeight) + 'px';
newBgDiv.style.height = imgHeight + 'px';
newBgDiv.style.width = '100%';
mybody.appendChild(newBgDiv);
}
to go through the js code quickly, I basically get the height of the page and divide it by the height of the picture to know how many divs i have to create to generate the entire background. Once done, you simply define the number of different row offset you want (offsetLines) and you generate your divs.
In my example, the offset depends on the size of the window (try to resize horizontally your window, you'll see that the offset changes). You can of course fix it to a defined number of pixels !
If CSS3 is appropriate for your project, you can use multiple backgrounds to do this.
Make an image that is twice the height of your pattern, with the top half filled with your pattern, and the bottom half transparent.
Then use multiple backgrounds to position them accordingly like so:
.div {
background: url(path/to/image.png) 0 0 repeat,
url(path/to/image.png) 0 {height of image in pixel} repeat
}
Its not a random offset, but with enough variations to this, it would be hard to tell the difference.
If you want to add to the perceived randomness, have a look at this post.

Making a Flex DataGrid scroll smoothly

I've noticed that the default behaviour for a DataGrid's vertical scroll bar is to scroll one row at a time. This is all well and good when the rows are all uniform and small (e.g. displaying a single line of text), but gets really ugly as soon as you have rows with variable heights.
I'm curious, is there a way to make DataGrid scrolling "smooth"? For instance, is there a way to have the DataGrid scroll by a set number of pixels, lines of text, etc. rather than scrolling one row at a time?
So far, the only solution I've managed to come up with is to place the DataGrid in a Canvas and have the Canvas do the scrolling instead of the DataGrid. The issue with this approach, though, is that as soon as the Canvas scrolls far enough, the DataGrid headers scroll off-screen. Ideally, I'd like to get the smooth-scrolling nature of the Canvas, but also keep the DataGrid headers visible. Is that possible?
The way that ItemRenderer's work in Flex 3 makes smooth scrolling difficult to achieve. Basically Flex recycles item renderers scrolled off of the top of the list as the display objects used for new data at the bottom of the list. Adobe's implementation of most list components in Flex 3 creates and adds these items as they come on to the screen rather than just off the screen, so they "pop in" and smooth scrolling isn't available. I'm not sure why they couldn't have done it in a similar manner for items +/- one position above or below the current scroll pane, but they didn't, and we're stuck with sticky scrolling by default.
Work-arounds do exist, though the one you've noted (dropping the datagrid into a canvas) negates the display-object saving intention of item renderers and incurs a performance cost. This will be fixed for most list-based Flex components in Flex 4, though it won't be fixed immediately for DataGrid. The DataGrid / AdvancedDataGrid component is maintained by a separate team based in India, last time I heard, and so it tends to be a bit behind the rest of the SDK.
I'd recommend trying something similar to this implementation of a smooth-scrolling list by Alex Harui. I'm not sure exactly how well it'd work for DataGrid or AdvancedDataGrid, but this is the most intuitive technique I can think of for making the list scroll correctly.
Try this... It's still based on Alex's code that was mentioned above. His should still be a great start for removing the snap-to-row behavior. Original source:
http://blogs.adobe.com/aharui/2008/03/smooth_scrolling_list.html
Alex's original some code for smooth vertical scrolling but that was not an issue I had with the DataGrid. It was smooth scrolling horizontally that I needed. I am using the DataGrid in an unorthodox manner for analyzing plain text reports output by our database (great way of providing visual feedback on a document). The code below allows content to go off screen and the user can scroll without that snap-to-column behavior.
You can adapt this to use the same math routines for vertical scrolling and then it will make scrolling possible and ignore the snap to row behavior. In particular switch the usage of the listContent.move method to move the contents vertically and use a inverse of the rounded pixel value you calculate from the vertical scroll bar (as opposed to my using the horizontal).
This method is bit simpler than Alex's method from the link above - a lot less code so try adapting and see how it works.
override protected function scrollHandler(event:Event):void
{
// Override the default scroll behavior to provide smooth horizontal scrolling and not the usual "snap-to-column" behavior
var scrEvt:ScrollEvent = event as ScrollEvent;
if(scrEvt.direction == ScrollEventDirection.HORIZONTAL) {
// Get individual components of a scroll bar for measuring and get a horizontal position to use
var scrDownArrow:DisplayObject = horizontalScrollBar.getChildAt(3);
var sctThumb:DisplayObject = horizontalScrollBar.getChildAt(2);
// I replaced maxHorizontalScrollPosition in Alex's code with "1300" to fix my exact application. In other situations you may finding using some property or different value is more appropriate. Don't rely on my choice.
var hPos:Number = Math.round((sctThumb.y - scrDownArrow.height) / (scrDownArrow.y - sctThumb.height - scrDownArrow.height) * 1300);
// Inverse the position to scroll the content to the left for large reports
listContent.move(hPos * -1, listContent.y);
}
// Go ahead and use the default handler for vertical scrolling
else {
super.scrollHandler(event);
}
}

How do you set the proper width of a dynamic UITextField before assigning text?

In Flex 3.2, I'm creating a UITextField, then measuring text that I'm about to assign to that field's text property. I then use those metrics to set the size of the field. However, the calculated width is not wide enough to accommodate the text. Is there a different order to achieve proper measurement or am I seeing a problem with the measureText() function? How can I get accurate results?
// UITextField's default size appears to be 100x100
// Measure the text then set width and height
var tf:UITextFormat = uiTextField.getUITextFormat();
var tlm:TextLineMetrics = tf.measureText(this.labelText);
// Text within the field is clipped unless 'padding' is added to the size
// Flex Documentation specifies there is a 2 px gutter on each side, so
// the expected padding would be 4 px. However, clipping occurs, for
// "Hello, World" up to 9 px.
uiTextField.width = tlm.width + 9;
uiTextField.height = tlm.height + 4;
uiTextField.border = true;
uiTextField.name = "uiTextField";
uiTextField.text = this.labelText;
I've had all sorts of trouble with measuring the width and heights of textFields before. It looks like you just want to autosize the textField. Have you tried:
uiTextField.autoSize = TextFieldAutoSize.LEFT;
???
Unfortunately, Flex often gets very confused when it tries to dynamically get a textWidth. Even worse, it is difficult to reliably catch the error and have Flex update itself correctly. The best options I've found:
Hack it manually -- mostly reliable and has the benefit of happening before everything has finished rendering:
mx.controls.Text hack AS3 describes a way to do this.
Use callLater or an FlexEvent.CREATION_COMPLETE event listener -- this is less reliable, but it is definitely not a hack
Use setTimeout with a delay of less than 1/10 a second ( I like 25-50 milliseconds ) -- I have found this the most reliable, but it may cause a small "blip" on the screen (generally not terribly noticeable for short code).

Resources