JQuery Mobile 1.4.5 right panel animation - css

I'm using a JQuery Mobile 1.4.5 external panel on the right side of some pages, and the pages have also the class ui-responsive-panel, so that the main content is resized according to the panel width to show its whole content.
The panel has also data-dismissible="false" because in the main content are some form controls and user interaction while the panel is open.
The panel shows itself through the "reveal" animation, which causes the page main content first to swipe off to the leftmost side of the page, and then resize to the remaining page width.
<div data-role="panel" data-position="right" data-position-fixed="true" data-display="reveal" data-dismissible="false">
</div>
This is the CSS i'm use to customize the JQM right panel:
.ui-panel {
width: 25em;
}
.ui-responsive-panel .ui-panel-page-content-open.ui-panel-page-content-position-right {
margin-left: 25em;
}
.ui-panel-page-content-position-right,
.ui-panel-dismiss-open.ui-panel-dismiss-position-right {
left: 0;
right: -25em;
}
.ui-panel-animate.ui-panel-page-content-position-right.ui-panel-page-content-display-reveal {
left: 0;
right: 0;
-webkit-transform: translate3d(-25em,0,0);
-moz-transform: translate3d(-25em,0,0);
transform: translate3d(-25em,0,0);
}
Is there a simply way to avoid the initial left swipe-off of the leftmost main part of the page, and instead to have the main content smoothly decrease its size from the right side during the panel animation?

This below is a possible solution using the jQuery animate() method. Let say its enough to make only the page-content responsive, as the full-page animation (toolbars included) is not a requirement:
$(".ui-panel").on("panelbeforeopen", function (event, ui) {
$(".ui-page-active .ui-content").animate({
"margin-right": "50%"
}, 300,"swing");
});
$(".ui-panel").on("panelbeforeclose", function (event, ui) {
$(".ui-page-active .ui-content").animate({
"margin-right": "0%"
}, 300,"swing",function()
{
$(this).removeAttr("style");
});
});
Furthermore, when used with a Panel with data-display="overlay" the class "ui-responsive-panel" in the body is no longer necessary and can be dropped, because the Panel height can be easily adapted to fill only the page-content height (credits: jQuery mobile panel between header and footer) .
function scalePanelToContent() {
var screenH = $.mobile.getScreenHeight();
var headerH = $(".ui-header").outerHeight() - 1;
var footerH = $(".ui-footer").outerHeight() - 1;
var panelH = screenH - headerH - footerH;
$(".ui-panel").css({
"top": headerH,
"bottom": footerH,
"min-height": panelH
});
}
By adding "bottom": footerH the Panel is nicely filling the full page-content height also when the browser window is resized.
Panel: the CSS Panel animation is adapted with a minimum of changes, using the same criteria that jQuery actually uses for transitions and animations support:
.ui-panel-animate.ui-panel-position-right.ui-panel-display-overlay {
-webkit-transform: translate3d(100%,0,0);
transform: translate3d(100%,0,0);
-moz-transform: translate3d(100%,0,0);
}
The final result from user-experience point of view, is very closely as using a Panel with data-display="push" or data-display="reveal" in a Panel responsive page: a nice smooth animation - moreover without the overhead of the panel wrapper on each JQM page and with the header and footer toolbars always available at full width without shrinking.
Full demo: http://jsfiddle.net/e6Cnn/38/

Related

How do I create CSS scroll effects?

When the user scrolls the website downward and reaches a certain point, I want it to trigger a certain behavior. Example could be a change in text position.
Here's an example of what you are describing using javascript:
function winScroll() {
if (window.pageYOffset > 1000) {
window.alert('You have scrolled down the page more than 1000 pixels');
}
}
window.addEventListener('scroll',winScroll,false);
body {
height: 4000px;
}
<h1>Keep scrolling down...</h1>

Ipad Safari - Can't scroll page if scrolling inside iframe

Is it possible to continue scrolling through a webpage even if you are touching inside an iframe? This problem only happens with iOS devices and I couldn't find any solutions for this!
My current page contains an iframe in the middle with width:95% and about 500px height, so when I reach the iframe I can't scroll any more (unless I touch very close to the sides).
Thanks
In my case, I had full access to the iframe and was dynamically inserting its content. Still, none of the solutions suggested by Brandon S worked. My solution:
Create a transparent div overlaying the iframe.
Capture any click events on the overlay and replicate them within the iframe (to allow the user to click on links/buttons)
This works because the overlaying div is part of the outer document, making it respond to touch/click events normally, and prevents the user from directly interacting with the iframe content.
Html Template:
<div style="position: relative;">
<div
style="position: absolute; top: 0; right: 0; bottom: 0; left: 0; opacity: 0;"
ng-click="$ctrl.handleOverlayClick($event)"
></div>
</div>
Controller (AngularJS component)
...
constructor ($document, $element) {
this.iframe = $document[0].createElement('iframe');
this.iframe.width = '100%';
this.iframe.height = '100';
this.iframe.sandbox = 'allow-same-origin allow-scripts allow-popups allow-forms allow-top-navigation';
const element = $element[0].children.item(0);
element.appendChild(this.iframe);
this.contentDocument = this.iframe.contentDocument;
}
handleOverlayClick ($event) {
// Overlay element is an invisible layer on top of the iframe. We use this to
// capture scroll events which would be in the iframe (which don't work properly on iPad Safari)
// When a click is detected, we propigate that through to the iframe so the user can click on links
const rect = $event.target.getBoundingClientRect();
const x = $event.clientX - rect.left; // x position within the iframe
const y = $event.clientY - rect.top; // y position within the iframe
// triggering click on underlaying element
const clickedElement = this.contentDocument.elementFromPoint(x, y);
clickedElement && clickedElement.click();
}
It sounds like the iframe is receiving the user's scroll event, instead of the page. This can happen when part of the iframe's content doesn't fit within the size of the iframe element.
A solution to this problem is to stop the iframe from ever trying to scroll. There are few ways to accomplish this:
In iframe's HTML, add this CSS:
html, body {
overflow: hidden
}
If you don't have access to the iframe's HTML (because maybe the iframe is loading a 3rd party's content), you can put a wrapper div around the iframe and disable scrolling that way. Add this to the parent page HTML:
<div style="overflow: hidden"><iframe src="example.com"></iframe></div>
You can add this to the parent page HTML CSS to make browser use momentum so that ends up scrolling past the bottom of the iframe and then scrolls the page:
*{
-webkit-overflow-scrolling: touch
}
Add the legacy "scrolling" attribute to the iframe to stop the iframe from trying to scroll:
<iframe src="example.com" scrolling="no"></iframe>

Bootstrap modal at top of iframe regardless of scroll position. How do I position it on screen?

When embedding a Bootstrap app in an iframe, modal dialogs always open at the top of the iframe, not the top of the screen. As an example, go to http://getbootstrap.com/javascript/ and open an example modal on the page. Then using the sample code below which places the same bootstrap page in an iframe, find a modal and open it:
<html>
<head>
</head>
<body>
<table width="100%">
<tr><td colspan="2" style="height:80px;background-color:red;vertical-align:top">Here's some header content I don't control</td></tr>
<tr><td style="width:230px;height:10080px;background-color:red;vertical-align:top">Here's some sidebar content I don't control either</td>
<td valign="top">
<iframe width="100%" height="10000px"
scrolling="no" src="http://getbootstrap.com/javascript/">
</iframe>
</td>
</tr>
</table>
</body>
</html>
Demo in fiddle
How do I go about positioning the modal on the screen in this scenario?
UPDATE: Unfortunately, my iFrame cannot fill the screen, nor can I make it fixed since it needs to blend into the rest of the page and the page itself has enough content to scroll. This is not my design and I ultimately intend to rework the whole thing, but this is what I have to work around for now. As a temporary option, I'm using javascript to tell the iframe parent to scroll to the top where the modal dialog pops up. While this is acceptable, this isn't the desired behavior.
I'm using angularjs and the ui-bootstrap library in my code but as you can see above, it's a bootstrap issue.
If your iframe has the same document.domain as the parent window or it is a sub domain, you can use the code below inside the iframe:
$('#myModal').on('show.bs.modal', function (e) {
if (window.top.document.querySelector('iframe')) {
$('#myModal').css('top', window.top.scrollY); //set modal position
}
});
show.bs.modal will fire after you call $('#myModal').show()
window.top.scrollY will get the scrollY position from the parent window
In case your document.domain differs from the parent, you can hack it getting the onmousedown position inside the iframe. For example:
$('#htmlElement').on('mousedown', function (event) {
event.preventDefault();
$('#myModal').data('y', event.pageY); // store the mouseY position
$('#myModal').modal('show');
});
$('#myModal').on('show.bs.modal', function (e) {
var y = $('#myModal').data('y'); // gets the mouseY position
$('#myModal').css('top', y);
});
Quite old question but I don't see the solution/workaround I've found. It might be helpful for someone in the future.
I had the same issue - my iFrame doesn't fill the entire screen, it displays bootstrap's modal and it is loading content from different domain than the parent page.
TL;DR
Use window.postMessage() API - Documentation here. for communication between parent and iframe (cross-domain)
pass message with currentScrollPosition and Y position of your iframe
Reveive message and update modal's padding from the top
In my case the workaround was to use window.postMessage() API - Documentation here.
It requires to add some extra code to the parent and handle message in an iFrame.
You can add EventListener and listen to 'scroll' event. Each time the event handling function is invoked you can get currentScrollPosition like document.scrollingElement.scrollTop.
Keep in mind that your iframe can have some margin from the top in the parent page so you should also get its 'offset'.
After that you can post these two values to your iframe e.g. ncp_iframe.contentWindow.postMessage(message, '*');
Note that the message has to be a String value
After that in your iFrame you need to add EventListener and listen to 'message' event.
The event handling function will pass your 'message' in event.data property. Having that you can update modal padding. (Looks much better if you don't use animations e.g. fade, in classes);
Quick Example:
Parent:
window.addEventListener('scroll', function(event){
var myIframe = document.querySelector('#myIframe');
var topOffset = myIframe.getBoundingClientRect().top + window.scrollY;
var currentScroll = document.scrollingElement.scrollTop;
myIframe.contentWindow.postMessage(topOffset + ':' + currentScroll, '*');
});
iFrame:
window.addEventListener('message', function(event) {
var messageContent = event.data.split(':');
var topOffset = messageContent[0];
var currentScroll = messageContent[1];
//calculate padding value and update the modal top-padding
}, false);
This is a bug in most browsers (IE appears fine) where the elements are fixed to the iframe, not the window. Typically, if you need something to be relative to the main document, it has to be in the main document.
A workaround is to wrap your iframe in a fixed position div that takes up the whole width of the screen and then maximize the iframe within that. This appears to resolve the issue
HTML:
<div class="fixframe">
<iframe src="http://getbootstrap.com/javascript/"></iframe>
</div>
CSS:
.fixframe {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
}
.fixframe iframe {
height: 100%;
width: 100%;
}
Working Demo in fiddle
See Also:
position:fixed inside of an iframe
iframe with a css fixed position : possible?
position fixed div in iframe not working
It is because the class .modal has position: fixed attribute. Try position: relative instead.

Left align Google chart in a page using bootstrap 3?

In my page, I have the below divs
<div><h2>Test</h2></div>
<div id="chart_div" style="width: 1200px; height: 600px;"></div>
I don't have any style, other than the default provided by bootstrap 3.
But the google chart is rendered with so much empty space at left (see the screenshot)
is there a way to fix this?
Fix Update :
As per davidkonrad suggestion, I added the below option in my chart
chartArea : { left: 30, top:30 }
Now it works
It is not caused by bootstrap - can easily reproduce the behaviour in a "fresh" bootstrap 3. It is caused by the enormous width of the container.
When chartArea is not defined, then chartArea.left, top, width and height is per default set to auto, which means the chart tries to center itself inside the container, both vertically and horizontally. You can observe that yourself by setting a border around the container. Set chartArea.left to force the chart-position where you want it, for example :
var options = {
chartArea : { left: 80 }
};
or
var options = {
chartArea : { left: "10%" }
};

fixed vertical positioning of css within an iframe

I am trying to get my bottom header to stick to the bottom of the screen inside of my iframe application and have it always appear in view for the user even when the page is scrolling. I have no control over the outer iframe as it is on a different domain. The header itself must be inside of the iframe as I have no control outside the iframe. The iframe always expands to the height of its contents so that it has no scrollbars, but the bar still has to be visible in the viewport at all times.
Another thing to note: The iframe height should be the same height as its contents so their is no need for scroll bars
Chrome has a bug that doesn't fix elements with position:fixed if:
a) you use CSS3 transform in any element, and/or
b) you have a child element positioned outside the box of it's parent element
Oddly enough, the bug was reported back in 2009 and it's still open: https://code.google.com/p/chromium/issues/detail?id=20574
You might want to play around with position: fixed;
#element {
position: fixed;
z-index: 1000;
bottom: 0;
}
EDIT:
I'm sorry, I think I miss understood your post. If I'm reading it correctly you want to create a header bar similar to blogger but to keep it always in view of the user when he/she scrolls.
What you can do is create a container div, and then you can nest both your header and iframe inside that container. You can then play around with the positioning, although I'm not sure if the exact behavior that you're looking for is possible without some javascript.
EDIT 2:
After playing around a bit, I got something that I think might help (if I understand your problem correctly).
http://digitaldreamer.net/media/examples/iframe-site.html
http://digitaldreamer.net/media/examples/iframe.html
I had to look for a long time for a possible solution, and I think I have found one that is using the Intersection Observer API to detect the scrolled position of the iframe within the parent document without needing to access the parent document DOM.
I'm creating a bunch of hidden 100px high elements in the iframe. These are positioned absolutely underneath each other so that together they fill the height of the whole iframe document. An intersection observer then observes the intersection between the (top-level document) viewport and each of the hidden elements and calculates the scroll position of the iframe based on the values it returns. A ResizeObserver creates additional hidden elements if the height of the body increases.
This approach assumes that your iframe is always minimum 100px high. If you expect a smaller height, you need to adjust the hidden container height. The reason is that once a hidden container is 100% visible, the intersection observer does not emit the callback while the parent document is being scrolled (since the intersection ratio stays at 1). This is also the reason why I need a lot of small containers rather than observing the intersection with the iframe body itself.
const CONTAINER_HEIGHT = 100;
const threshold = [...Array(CONTAINER_HEIGHT + 1).keys()].map((i) => i / CONTAINER_HEIGHT);
/**
* Registers an intersection handler that detects the scrolled position of the current
* iframe within the browser viewport and calls a handler when it is first invoked and
* whenever the scrolled position changes. This allows to position elements within the
* iframe in a way that their position stays sticky in relation to the browser window.
* #param handler Is invoked when the function is first called and whenever the scroll
* position changes (for example due to the user scrolling the parent document). The
* "top" parameter is the number of pixels from the top of the browser viewport to the
* top of the iframe (if the top of the iframe is above the top of the browser viewport)
* or 0 (if the top of the iframe is below the top of the browser viewport). Positioning
* an element absolutely at this top position inside the iframe will simulate a sticky
* positioning at the top edge of the browser viewport.
* #returns Returns a callback that unregisters the handler.
*/
function registerScrollPositionHandler(handler: (top: number) => void): () => void {
const elementContainer = document.createElement('div');
Object.assign(elementContainer.style, {
position: 'absolute',
top: '0',
bottom: '0',
width: '1px',
pointerEvents: 'none',
overflow: 'hidden'
});
document.body.appendChild(elementContainer);
const elements: HTMLDivElement[] = [];
let intersectionObserver: IntersectionObserver | undefined = undefined;
const resizeObserver = new ResizeObserver(() => {
intersectionObserver = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.intersectionRatio > 0 && (entry.intersectionRect.top > entry.boundingClientRect.top || entry.target === elements[0])) {
handler(entry.intersectionRect.top);
}
}
}, { threshold });
const count = Math.ceil(document.documentElement.offsetHeight / CONTAINER_HEIGHT);
for (let i = 0; i < count; i++) {
if (!elements[i]) {
elements[i] = document.createElement('div');
Object.assign(elements[i].style, {
position: 'absolute',
top: `${i * CONTAINER_HEIGHT}px`,
height: `${CONTAINER_HEIGHT}px`,
width: '100%'
});
elementContainer.appendChild(elements[i]);
intersectionObserver.observe(elements[i]);
}
}
});
resizeObserver.observe(document.documentElement);
return () => {
resizeObserver.disconnect();
intersectionObserver?.disconnect();
elementContainer.remove();
};
}
This example code should create a toolbar that is sticky at the top of the browser viewport:
<div style="position: absolute; top: 0; left: 0; bottom: 0; right: 0; overflow: hidden; pointer-events: none; z-index: 90">
<div id="toolbar" style="position: absolute; top: 0; left: 0; right: 0; pointer-events: auto; transition: top 0.3s">
Line 1<br/>Line 2<br/>Line 3<br/>Line 4<br/>Line 5<br/>Line 6<br/>Line 7<br/>Line 8<br/>Line 9<br/>Line 10
</div>
</div>
<script>
registerScrollPositionHandler((top) => {
document.querySelector('#toolbar').style.top = `${top}px`;
});
</script>
Note that other than what you asked for, this will position the toolbar at the top of the viewport rather than at the bottom. Positioning at the bottom should also be possible, but is slightly more complex. If anyone requires a solution for this, please let me know in the comments and I will invest the time to adjust my answer.

Resources