BootStrap Modal background scroll on iOS - ios

So there is this known issue with modal on iOS, when the modal is enabled swiping up/down will scroll the body instead of the modal.
Using bootstrap 3.3.7
Tried to google it, most suggested adding
body.modal-open {
overflow: hidden !important;
}
but it doesn't work.
Some suggested,
body.modal-open {
position: fixed;
}
But background will jump to the top of the page.
So for now I am using,
body.modal-open {
overflow: hidden !important;
position: fixed;
width: 100%;
}
#exampleModal {
background: black;
}
As a work-around so the jump can't be seen(but still noticeable)
Is there other solutions to this?
This is the site i am working on http://www.einproductions.com/

I've taken the solutions of #Aditya Prasanthi and #JIm, since one fixes the background-scrolling and the other fixes the skip-to-the-top after closing the modal, and turned them into one bare-minimum JS script:
let previousScrollY = 0;
$(document).on('show.bs.modal', () => {
previousScrollY = window.scrollY;
$('html').addClass('modal-open').css({
marginTop: -previousScrollY,
overflow: 'hidden',
left: 0,
right: 0,
top: 0,
bottom: 0,
position: 'fixed',
});
}).on('hidden.bs.modal', () => {
$('html').removeClass('modal-open').css({
marginTop: 0,
overflow: 'visible',
left: 'auto',
right: 'auto',
top: 'auto',
bottom: 'auto',
position: 'static',
});
window.scrollTo(0, previousScrollY);
});
It's, of course, possible and even adviced to use a class to set and unset the CSS for the body, however, I choose this solution to resolve a problem just in one place (and not require external CSS as well).

refer to Does overflow:hidden applied to <body> work on iPhone Safari?
Added .freezePage to html and body when modal is showing
$('.modal').on('shown.bs.modal', function (e) {
$('html').addClass('freezePage');
$('body').addClass('freezePage');
});
$('.modal').on('hidden.bs.modal', function (e) {
$('html').removeClass('freezePage');
$('body').removeClass('freezePage');
});
the CSS
.freezePage{
overflow: hidden;
height: 100%;
position: relative;
}

My solution:
scrollPos = window.scrollY - get current scroll position.
body { position: fixed;
margin-top: -**scrollPos** px);
}
Then modal is closed:
body {position: "";
margin-top: "";
}
and return scroll position to point before opened modal window:
window.scrollTo(0, scrollPos);

None of the above answers worked for me, the modal kept disappearing and I ended up with a brute force approach which is ugly and inefficient but works !
$('body').on('touchstart touchmove touchend', e => {
let scrollDisabled=$('.scroll-disable');
if (scrollDisabled.length > 0 && scrollDisabled.has($(e.target)).length===0) {
e.preventDefault();
e.stopPropagation();
}
});
setInterval(() => $('.modal:visible').css('top', '20px'), 100);
$(document).on({
'show.bs.modal': e => $(e.target).addClass('scroll-disable'),
'hidden.bs.modal': e => $(e.target).removeClass('scroll-disable')
}, '.modal');

Import this file and use the enableBodyScroll and disableBodyScroll functions to lock and unlock the body scroll.
using css top property will exactly navigate back to the previous position. It eliminate the drawback of dealing with the floating point margin.
const toggleBodyScroll = (position, initialMargin) => {
document.body.style.position = position;
document.body.style.top = initialMargin;
};
const getScrolledPosition = () => {
return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
};
const scrollToPrevPosition = (scrolledPosition) => {
window.scrollTo(0, scrolledPosition);
};
const getWindowTop = () => {
return window.getComputedStyle(document.body).getPropertyValue('top');
};
export const disableBodyScroll = () => {
toggleBodyScroll('fixed', `-${getScrolledPosition()}px`);
};
export const enableBodyScroll = () => {
const scrollPosition = 0 - parseInt(getWindowTop());
toggleBodyScroll('static', 0);
scrollToPrevPosition(scrollPosition);
};

This will prevent page scrolling while Modal is opened on iOS mobile
if ($(window).width() < 960) {
let previousScrollY = 0;
$(document).on('show.bs.modal', () => {
previousScrollY = window.scrollY;
$('html').addClass('modal-open').css({
marginTop: -previousScrollY,
overflow: 'hidden',
left: 0,
right: 0,
top: 0,
bottom: 0,
position: 'fixed',
});
}).on('hidden.bs.modal', () => {
$('html').removeClass('modal-open').css({
marginTop: 0,
overflow: 'visible',
left: 'auto',
right: 'auto',
top: 'auto',
bottom: 'auto',
position: 'static',
});
window.scrollTo(0, previousScrollY);
});
}

Related

How to work-around iOS fullscreen by minimizing URL bar

Actually I am trying to implement the solution available here https://im2.ezgif.com/tmp/ezgif-2-2f480da723.gif.
The hand, after touching and making finger move, minimize UI and opens, let say, something similar to the fullscreen.
For some reason code below isn't working on some Safari devices – iPhone 12 pro, 13 and 14 work correctly, while iPhone XS and XR don't.
my TS:
merge(fromEvent(window, 'scroll'), fromEvent(window, 'resize'), fromEvent(window, 'orientationchange'), of(null))
.pipe(takeUntil(this.destroy$))
.subscribe((e) => {
if (window.innerHeight > window.innerWidth) {
this.isFullscreen = window.innerHeight > document.documentElement.clientHeight + 50;
} else {
this.isFullscreen = window.innerHeight === document.documentElement.clientHeight;
}
if (this.isFullscreen) {
document.querySelector('html').classList.add('is-fullscreen');
window.scrollTo(0, 500);
} else {
document.querySelector('html').classList.remove('is-fullscreen');
document.querySelector('html').classList.remove('ios-scroll');
}
});
ngAfterViewInit() {
const elem = this.elemRef.nativeElement.querySelector('.ios-wrapper');
if (elem) {
elem.addEventListener('gesturestart', (e) => {
e.preventDefault();
});
elem.addEventListener('gestureend', (e) => {
e.preventDefault();
});
elem.addEventListener('gesturechange', (e) => {
e.preventDefault();
});
}
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
document.querySelector('html').classList.remove('ios-scroll');
}
HTML:
<div class="ios-wrapper" *ngIf="access" [ngClass]="{ 'is-fullscreen': isFullscreen }">
<div class="ios-hand">
<i class="far fa-hand-pointer"></i>
</div>
</div>
SCSS:
.ios-wrapper {
visibility: visible;
opacity: 1;
width: 100%;
height: 1000000px;
z-index: 9999;
position: absolute;
top: -500px;
left: 0;
margin: 0;
padding: 0;
background: rgba(0,0,0,0.5);
&.is-fullscreen {
visibility: hidden !important;
opacity: 0 !important;
z-index: 0;
pointer-events: none;
}
}
My purpose is to implement the solution which is working on every Safari device. Do you know where is the mistake or why it's not working on some iPhones?

How can an ag-Grid cell editor in Svelte prompt the user to confirm a change?

I have an ag-Grid in a Svelte file.
One of the column definitions is for a floating point number displayed to 2 places of decimals, like this:
const columnDefinitions = [
...
{
field: fixedScr,
headerName: "Fixed SCR",
cellClass: numberCellClassSelector,
type: "rightAligned",
width: 150,
editable: true,
valueFormatter: numberFormatterFactory(2),
valueParser: numberParser,
},
...
];
I have chosen the ag-Grid as a convenient means of displaying and editing a column of these values. However, my Product Owner wants the web page to challenge the user every time they make a change to a cell with an "Are you sure?" prompt.
A bit heavy-handed, perhaps, as it will make editing with the ag-Grid somewhat slower. But these values will be change infrequently, and changes should be made with care.
How would I define a simple cell editor, just for this column, which prompts the user to confirm a change before the grid is updated?
I would propose binding into an ag-grid event which is triggered once a value is updated. on the callback (which should by an async function).
my implementation will go as follow create a Popup.svelte component.
you will also create a store, which i will call popup in a global js file for example store.js.
you will then import popup from store.js in Popup.svelte. then you will set the value of the popup store to an async function which will interact with the HTML of Popup.svelte. this async function will return a promise which you will await in your other svelte components while using the popup store.
in this Promise you will await all previous popups to close to show your current popup, you will supply the title, and the return values of the buttons which will be shown in the popup
here is an example of the implementation of the code i made
<style>
.u-overlay {
min-height: 100vh;
max-height: 100vh;
width: 100%;
position: fixed;
display: flex;
background-color: rgba(0, 0, 0, 0.5);
z-index: 50000;
}
.u-box {
width: 500px;
background-color: white;
min-height: 100px;
margin: auto;
padding: 20px;
border-radius: 4px;
}
.u-title {
width: 100%;
text-align: center;
font-size: 22px;
}
.u-desc {
padding: 20px 20px;
text-align: center;
margin: 0;
}
.u-buttons {
width: 100%;
display: flex;
padding: 10px 0;
justify-content: space-evenly;
}
.u-over-button {
width: 150px;
border: 1px solid transparent;
border-radius: 4px;
color: white;
text-align: center;
padding: 8px 0;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: pointer;
}
</style>
<script>
import { popup } from "../utils.js";
import { fade } from "svelte/transition";
let overlay;
let template = {
title: "Write title",
desc: "Please select",
buttons: [
{ name: "OK", value: true, color: "#F0F0F0" },
{ name: "Decline", value: false, color: "red" },
],
};
let popData = undefined;
let promises = [];
let colorsConver = {
ok: "#46b978",
danger: "#d23149",
};
popup.set(async (data) => {
/* we got a new sub*/
/* start the promise for the future click */
let pro = new Promise(async (resolve, reject) => {
/* make sure all promises before this one are done */
await Promise.all(promises);
/* when they are done start the overlay for this sub */
/* convert text to appropriate hex */
for (let btn of data.buttons) {
if (colorsConver[btn.color]) {
btn.color = colorsConver[btn.color];
}
}
popData = data;
setTimeout(() => {
overlay.addEventListener(
"click",
(event) => {
if (event.target !== event.currentTarget) return;
event.stopPropagation();
console.log("from overlay");
resolve(data.buttons[data.buttons.length - 1].value);
popData = undefined;
},
{
once: true,
capture: true,
}
);
for (let b of [
...document.querySelectorAll(".u-overlay .u-buttons"),
]) {
b.addEventListener(
"click",
(event) => {
event.stopPropagation();
console.log("ending button");
resolve(event.target.dataset.res);
popData = undefined;
},
{
once: true,
capture: true,
}
);
}
}, 130);
});
/* add this promise so the future ones wait it*/
promises.push(pro);
return pro;
});
import { popup } from "path/to/store.js";
const someFunction = () => {
let resp = await $popup({
title: "Write title",
desc: "Please select",
buttons: [
{ name: "OK", value: true, color: "#F0F0F0" },
{ name: "Decline", value: false, color: "red" },
],
});
};
</script>
{#if popData}
<div
bind:this={overlay}
transition:fade={{ duration: 150 }}
class="u-overlay"
>
<div on:click|stopPropagation|preventDefault class="u-box">
<div class="u-title">{popData.title}</div>
<p class="u-desc">{popData.desc}</p>
<div class="u-buttons">
{#each popData.buttons as b}
<div
data-res={b.value}
class="u-over-button"
style={"background-color:" + b.color}
>
{b.name}
</div>
{/each}
</div>
</div>
</div>
{/if}
in other components
<script>
import { popup } from "path/to/store.js";
const someFunction = () => {
let resp = await $popup({
title: "Write title",
desc: "Please select",
buttons: [
{ name: "OK", value: true, color: "#F0F0F0" },
{ name: "Decline", value: false, color: "red" },
],
});
};
</script>

How to set zindex in konva.js?

I have a problem with the Zindex in konva.js. After I added everything to the layer
I am trying to assign a property to a node for each element separately. But it does not work. For example
for(let i = 0; i<=this.layer['children']; i++){
this.layer['children'][i].setZIndex(someInt);
}
How can i set zindex for all elements in layer?
zIndex in Konva is just index of the element in an array of children of the parent element. So you can't set any number to it and it can not be bigger than children.length - 1.
Working snippet illustrating the getZIndex(), setZIndex(), moveUp() and moveDown() methods of the Kovajs.Shape object. See also example at Konvajs site.
Buttons allow use to move green circle up and down the z-index list by increments of 1, then also to try to move up +10 and down -100. Resulting z-index is shown in text below circles.
// Create the stage
var stage = new Konva.Stage({
container: 'container',
width: $('#container').width(),
height: $('#container').height()
});
// create the layer
var layer = new Konva.Layer();
var data = [ {x: 60, color: 'red'}, {x: 90, color: 'limegreen'}, {x: 120, color: 'gold'}]
var circles = [];
for (var i = 0; i < data.length; i = i + 1){
// create some circles
var circle = new Konva.Circle({
x: data[i].x,
y: 60,
radius: 50,
fill: data[i].color,
stroke: 'black',
strokeWidth: 2
});
layer.add(circle);
circles.push(circle);
}
stage.add(layer);
var green = circles[1];
function sayIndex(){
$('#info').html("Green zIndex=" + circles[1].getZIndex());
}
$('#greenup').on('click', function(){
green.moveUp();
sayIndex();
layer.draw();
})
$('#greendn').on('click', function(){
green.moveDown();
sayIndex();
layer.draw();
})
$('#greenup10').on('click', function(){
var ind = circles[1].getZIndex();
green.setZIndex(ind + 10);
sayIndex();
layer.draw();
})
$('#greendn100').on('click', function(){
var ind = circles[1].getZIndex();
green.setZIndex(ind - 100);
sayIndex();
layer.draw();
})
#container
{
width: 200px;
height: 150px;
border: 1px solid #666;
float: left;
}
#buttons
{
width: 200px;
height: 150px;
border: 1px solid #666;
float: left;
}
#info
{
position: absolute;
left: 20px;
top: 135px;
}
p
{
margin: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://cdn.rawgit.com/konvajs/konva/1.6.5/konva.min.js"></script>
<div id="container"></div>
<div id="buttons">
<p><button id='greenup'>Move Green + 1</button></p>
<p><button id='greendn'>Move Green -1</button></p>
<p><button id='greenup10'>Move Green +10</button></p>
<p><button id='greendn100'>Move Green -100</button></p>
<span id='info'></span>
</div>
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/konva#2.4.2/konva.min.js"></script>
<meta charset="utf-8">
<title>Konva Shape Layering Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #F0F0F0;
}
#buttons {
position: absolute;
left: 10px;
top: 0px;
}
button {
margin-top: 10px;
display: block;
}
</style>
</head>
<body>
<div id="container"></div>
<div id="buttons">
<button id="toTop">
yellow z-index -2
</button>
<button id="toBottom">
yellow -9
</button>
<button id="up">
yellow z-index 1
</button>
<button id="down">
yellow z-index -5
</button>
<button id="zIndex">
Set yellow box zIndex to 3
</button>
</div>
<script>
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
var offsetX = 0;
var offsetY = 0;
var colors = ['red', 'orange', 'yellow', 'green', 'blue', 'purple'];
var yellowBox = null;
for(var n = 0; n < 6; n++) {
// anonymous function to induce scope
(function() {
var i = n;
var box = new Konva.Rect({
x: i * 30 + 210,
y: i * 18 + 40,
width: 100,
height: 50,
fill: colors[i],
stroke: 'black',
strokeWidth: 4,
draggable: true,
name: colors[i]
});
box.on('mouseover', function() {
document.body.style.cursor = 'pointer';
});
box.on('mouseout', function() {
document.body.style.cursor = 'default';
});
if(colors[i] === 'yellow') {
yellowBox = box;
}
layer.add(box);
})();
}
stage.add(layer);
// add button event bindings
document.getElementById('toTop').addEventListener('click', function() {
yellowBox.setZIndex(-2);
layer.draw();
}, false);
document.getElementById('toBottom').addEventListener('click', function() {
yellowBox.setZIndex(-9);
layer.draw();
}, false);
document.getElementById('up').addEventListener('click', function() {
yellowBox.setZIndex(1);
layer.draw();
}, false);
document.getElementById('down').addEventListener('click', function() {
yellowBox.setZIndex(-5);
layer.draw();
}, false);
document.getElementById('zIndex').addEventListener('click', function() {
yellowBox.setZIndex(3);
layer.draw();
}, false);
</script>
</body>
</html>
Last
Fun messing with ZIndex code,
the setZIndex is all you need; It will vain the z-index inside Konva.JS
If you would prefer to be able to set the render order of nodes using arbitrary numbers (like CSS, or how it works in most game engines, etc), you can use this fork of Konva. Alternatively, you could also grab & apply just the rejected pull request for this feature if you already have a customized Konva version.
The new feature works by adding a zOrder property to all Nodes, and a special kind of group AbsoluteRenderOrderGroup that reads and understands this property for all children.

iOS Safari issue - Element becomes invisible while scrolling when changing position absolute to fixed

I want to use an element on the page as the title of the following content, but when the user is scrolling into the content this title-element should be fixed at the header. Similar to the ABC-captions in the iOS music-app.
See here: https://jsfiddle.net/1e7ync4w/
HTML
<div>
<div class="top">
Test
</div>
<div class="content">
<div class="scroller">
</div>
Test
</div>
</div>
CSS
.top {
background-color: yellow;
height: 300px;
}
.content {
position: relative;
height: 600px;
background-color: green;
}
.scroller {
position: absolute;
width: 100%;
height: 10px;
top: 0;
left: 0;
background-color: blue;
}
.scroller.fixed {
position: fixed;
}
JS
$(document).ready(function() {
$(window).on('scroll touchmove', function() {
$('.scroller').removeClass('fixed');
var scrollTop = $(window).scrollTop();
var scrollerOffsetTop = $('.scroller').offset().top;
if(scrollerOffsetTop <= scrollTop) {
$('.scroller').addClass('fixed');
}
});
});
The problem is that the iOS safari seems to have a bug with changing elements to fixed (via JavaScript) while scrolling. As soon as the user scrolls into the content, the title-element becomes invisible but shows after releasing the finger from the display (scroll-end).
I only tested this on the iOS 9.3.2 safari but I think this issue is older.
I found a solution for this problem. It's a little bit hacky but the only workaround I found for this iOS-bug.
The GPU of the browser needs to be "activated" for updating the according element. This can be achieved by setting a transform: translate-style via JS as soon as the positioning jumped to fixed.
The code of the example would look like this:
$(document).ready(function () {
$(window).on('scroll touchmove', function () {
$('.scroller').removeClass('fixed');
var scrollTop = $(window).scrollTop();
var scrollerOffsetTop = $('.scroller').offset().top;
if (scrollerOffsetTop <= scrollTop) {
$('.scroller').addClass('fixed').css({
'transform': 'translate3d(0px,0px,0px)',
'-moz-transform': 'translate3d(0px,0px,0px)',
'-ms-transform': 'translate3d(0px,0px,0px)',
'-o-transform': 'translate3d(0px,0px,0px)',
'-webkit-transform': 'translate3d(0px,0px,0px)'
});
}
});
});

Issue with hidden div-jquery

i'm using jquery hashchange technique for dynamically and smoothly(fadein) loading the contents in a website.Though it's dynamic the url gets changed in the addressbar which looks as follows..as you can see a pound symbol appears before filename.
www.whatever.com/#about.html
www.whatever.com/#contact.html and so on.
In my index page a div named 'folder1' is hidden by default and it's made visible 5seconds after the page is loaded and there is div folder2(check the code).
When you type url 'www.whatever.com' everything works as it should. But when you hit 'home' link it appends # to index.html so url will be whatever.com/#index.html.
And this time div 'folder2' show up right after page is loaded which should be hidden as per the code. I noticed css of those divs gets messed up this time.
I don't understand what's happpening there. Any help?
(function($,i,b){var j,k=$.event.special,c="location",d="hashchange",l="href",f=$.browser,g=document.documentMode,h=f.msie&&(g===b||g<8),e="on"+d in i&&!h;function a(m){m=m||i[c][l];return m.replace(/^[^#]*#?(.*)$/,"$1")}$[d+"Delay"]=100;k[d]=$.extend(k[d],{setup:function(){if(e){return false}$(j.start)},teardown:function(){if(e){return false}$(j.stop)}});j=(function(){var m={},r,n,o,q;function p(){o=q=function(s){return s};if(h){n=$('<iframe src="javascript:0"/>').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this);
$(function() {
var newHash = "",
$mainContent = $("#main-content"),
$pageWrap = $("#page-wrap"),
baseHeight = 0,
$el;
$pageWrap.height($pageWrap.height());
baseHeight = $pageWrap.height() - $mainContent.height();
$("nav").delegate("a", "click", function() {
window.location.hash = $(this).attr("href");
return false;
});
$(window).bind('hashchange', function(){
newHash = window.location.hash.substring(1);
if (newHash) {
$mainContent
.find("#guts")
.fadeOut(200, function() {
$mainContent.hide().load(newHash + " #guts", function() {
$mainContent.fadeIn(200, function() {
$pageWrap.animate({
height: baseHeight + $mainContent.height() + "px"
});
});
$("nav a").removeClass("current");
$("nav a[href="+newHash+"]").addClass("current");
});
});
};
});
$(window).trigger('hashchange');
});
above makes the entire jquery code.
this is the css.infact there are two divs.
#folder1{
float: left;
height: 100px;
width: 100px;
position: absolute;
top: 15px;
right: 100px;
display: none;
}
#folder2{
float: left;
height: 100px;
width: 100px;
position: absolute;
top: 15px;
right: 100px;
display: show;
}
below is the code usedto hide and show divs.
$(document).ready(function() {
$("#folder2").hide();
setTimeout(function(){
$("#folder1").show();
}, 5000);
setTimeout(function(){
$("#folder1").hide();
}, 10000);
setTimeout(function(){
$("#folder2").show();
}, 15000);
});

Resources