psychopy.org | Reference | Downloads | Github

Still trying to sort out embedded html scrolling across browsers

URL of experiment: Pavlovia

Description of the problem: I’ve been tinkering with my embedded html forms (based on Custom web component for online experiments: Forms, surveys, questionnaires, and other web-based content) because I now use it for demographics and integrated forms instead of Qualtrics. However, participants are still experiencing issues with it, specifically the inability to scroll (which means that the Submit button can’t be pressed).

On my Windows 10 Dell laptop it works fine using both Chrome and Edge.


(though there appears to be an issue with the calculated font size).

If you are reading this, please could you try it on your device (reporting back the device and browser and noting whether it scrolls or not).

The code is as follows:

// This routine should not start straight away or the screen width and height
// may be incorrect.

expInfo['xRes'] = screen.width;
expInfo['yRes'] = screen.height;
console.log("screen xRes",expInfo['xRes']);
console.log("screen yRes",expInfo['yRes']);

let src = ('demographics.html?xRes='+expInfo['xRes']+'&yRes='+expInfo['yRes']);

params = {};  // Results added here after form is submitted
continue_routine = true; // Routines can't be ended from within Begin Routine
$(document).ready(function() {
    // Add custom contents from html file using an iframe:
    $('body').append('<div id="iframe-o" style="visibility: hidden; position: relative; display: table; margin: auto;"><div id="iframe-m" style="display: table-cell; vertical-align: middle;"><div id="iframe-i" style="display: inline-block; width:100%; overflow-y: auto; overflow-x: hidden;"><iframe id="iframe" src="'+src+'" frameborder="0" style="width: 100%"></iframe></div></div></div>');

    $('#iframe').on('load',function(iframe){
        // Auto-adjust iframe size:
        // Auto-adjust iframe size:
        $(this).contents().find('html').css({ 'display': 'table', 'width': '100%', 'overflow-x': 'hidden' });
        $('#iframe-o').height($(window).height()-20, true);
        $('#iframe-m').width($(this).contents().find('html').width()+100);
        $('#iframe-i').height ( Math.min ( $(this).contents().find('html').height()+20, $(window).height()-20 ), true );
        $(this).height($(this).contents().find('html').height());
        $('#iframe-o').css('visibility','visible');

        // If iframe contains a form, then capture its output:
        $(this).contents().find('form').on('submit',function(e){
            e.preventDefault();
            $.each($(this).serializeArray(),function(i, param){
                params[param.name] = param.value;
                psychoJS.experiment.addData(param.name, param.value);
            });
            console.log ( 'DEBUG:FRM', params );
            // Remove iframe and continue to next routine when done:
            $('#iframe-o').remove();
            continue_routine = false;
        });
    });
});
//$('#iframe').attr( 'src', function(i,val){ return val;} );

And the html page is

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="user-scalable=no">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js">
	</script>
<script type="text/javascript">
	function getUrlVars() {
		var vars = {};
		var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) {
		vars[key] = value;
		});
		return vars;
	}
	function getUrlParam(parameter, defaultvalue){
		var urlparameter = defaultvalue;
		if(window.location.href.indexOf(parameter) > -1){
        	urlparameter = getUrlVars()[parameter];
	        }
		return urlparameter;
	}
	var xRes = parseInt(getUrlParam('xRes','30'));
	var yRes = parseInt(getUrlParam('yRes','30'));

if ((xRes > yRes)) {
    xRes = (xRes * 0.8);
    yRes = (yRes * 0.04);
} else {
    if ((xRes < yRes)) {
        xRes = (xRes * 2.4);
        yRes = (yRes * 0.06);
    } else {
        xRes = 1000;
        yRes = 30;
    }
}

</script>
<style type="text/css">
<!--
	.style1 {font-size: var(--yRes)px}
	.style2 {text-align: left}
	.style3 {background-color: #999999;
	}
	input[type=checkbox] { 
		width: 35px; 
		height: 35px; 
	} 
	input[type=radio] { 
        	width: 35px; 
		height: 35px; 
	} 
	.button {
		background-color: #4CAF50;
		border: none;
		color: white;
		padding: 20px;
		text-align: center;
		text-decoration: none;
		display: inline-block;
		font-size: var(--yRes)px;
		margin: 4px 2px;
		border-radius: 12px;
	}
-->
</style>
</head>
<body id="bodyForm">
	<script>
		document.getElementById('bodyForm').style = "width: " + xRes + "px; font-family: Arial, Helvetica, sans-serif; justify-content: left; background-color: grey; color: white; font-size: "+ yRes +"px";
	</script>
	<form name="form1" method="post" action="">
		<h2 class="style2">Demographics (font size <script>document.write(yRes);</script>)</h2>
		<p class="style2">Thank you for agreeing to take part. In order to help me describe my participants, please could you answer the following questions?</p>
		<table border="0" cellpadding="20">
			<tr>
				<td valign="top">What gender are you?</td>
				<td><input name="gender" type="radio" id="gender" value="1"> Woman<br>
				    <input name="gender" type="radio" id="gender" value="2"> Man<br>
				    <input name="gender" type="radio" id="gender" value="3"> Non-binary<br>
				    <input name="gender" type="radio" id="gender" value="4"> Different Gender Identity <input name="gender_text" type="text" class="style1"><br>
				    <input name="gender" type="radio" id="gender" value="5"> Prefer not to say</td>
			</tr>
			<tr>
				<td valign="top">How old are you?</td>
				<td><select id="age" name="age" id="age" class="style1">
					<option value=""> </option>
					<option value="17">Under 18</option>
					<option value="18">18</option> 
					<option value="19">19</option> 
					<option value="20">20</option> 
					<option value="21">21</option> 
					<option value="22">22</option> 
					<option value="23">23</option> 
					<option value="24">24</option> 
					<option value="25">25</option> 
					<option value="26">26</option> 
					<option value="27">27</option> 
					<option value="28">28</option> 
					<option value="29">29</option> 
					<option value="30">30</option> 
					<option value="31">31</option> 
					<option value="32">32</option> 
					<option value="33">33</option> 
					<option value="34">34</option> 
					<option value="35">35</option> 
					<option value="36">36</option> 
					<option value="37">37</option> 
					<option value="38">38</option> 
					<option value="39">39</option> 
					<option value="40">40</option> 
					<option value="41">41</option> 
					<option value="42">42</option> 
					<option value="43">43</option> 
					<option value="44">44</option> 
					<option value="45">45</option> 
					<option value="46">46</option> 
					<option value="47">47</option> 
					<option value="48">48</option> 
					<option value="49">49</option> 
					<option value="50">50</option> 
					<option value="51">51</option> 
					<option value="52">52</option> 
					<option value="53">53</option> 
					<option value="54">54</option> 
					<option value="55">55</option> 
					<option value="56">56</option> 
					<option value="57">57</option> 
					<option value="58">58</option> 
					<option value="59">59</option> 
					<option value="60">60</option> 
					<option value="61">61</option> 
					<option value="62">62</option> 
					<option value="63">63</option> 
					<option value="64">64</option> 
					<option value="65">65</option> 
					<option value="66">66</option> 
					<option value="67">67</option> 
					<option value="68">68</option> 
					<option value="69">69</option> 
					<option value="70">70</option> 
					<option value="71">71</option> 
					<option value="72">72</option> 
					<option value="73">73</option> 
					<option value="74">74</option> 
					<option value="75">75</option> 
					<option value="76">76</option> 
					<option value="77">77</option> 
					<option value="78">78</option> 
					<option value="79">79</option> 
					<option value="80">80</option> 
					<option value="81">81</option> 
					<option value="82">82</option> 
					<option value="83">83</option> 
					<option value="84">84</option> 
					<option value="85">85</option> 
					<option value="86">86</option> 
					<option value="87">87</option> 
					<option value="88">88</option> 
					<option value="89">89</option> 
					<option value="90">90</option>
					<option value="91">Over 90</option>
				</select></td>
			</tr>
			<tr>
				<td valign="top">Are you a student?</td>
				<td>
					<input name="student" type="radio" id="student" value="1"> Yes, undergraduate<br>
					<input name="student" type="radio" id="student" value="2"> Yes, taught postgraduate<br>
					<input name="student" type="radio" id="student" value="3"> Yes, research postgraduate<br>
					<input name="student" type="radio" id="student" value="4"> No
				</td>
			</tr>
			<tr>
				<td valign="top">Are you fluent in English?</td>
				<td>
					<input name="english" type="radio" id="student" value="1"> Yes, as a first language<br>
					<input name="english" type="radio" id="student" value="2"> Yes, as a second language<br>
					<input name="english" type="radio" id="student" value="3"> No
				</td>
			</tr>
			<tr>
				<td colspan="2" style="text-align:right"><input type="submit" name="Submit" value="Submit"  class="button"></td>
			</tr>
		</table>
	</form>
<p>Some<br>
extra<br>
lines<br>
have<br>
been<br>
added<br>
to<br>
the<br>
demo<br>
here<br>
to<br>
check<br>
test<br>
the<br>
scrolling.</p>
</body>
</html>

Edit: I realised that the version I posted above wouldn’t scroll on my own iPhone but if I replace

    $('body').append('<div id="iframe-o" style="visibility: hidden; position: relative; display: table; margin: auto;"><div id="iframe-m" style="display: table-cell; vertical-align: middle;"><div id="iframe-i" style="display: inline-block; width:100%; overflow-y: auto; overflow-x: hidden;"><iframe id="iframe" src="'+src+'" frameborder="0" style="width: 100%"></iframe></div></div></div>');

with

$('body').append('<div id="iframe-o" style="visibility: hidden; position: relative; display: table; margin: auto;width:90%;overflow-x: hidden;"><div id="iframe-m" style="display: table-cell; vertical-align: middle; overflow-y: auto; overflow-x: hidden;"><div id="iframe-i" style="display: inline-block; width:100%; overflow-y: auto; overflow-x: hidden;"><iframe id="iframe" src="'+src+'" frameborder="0" style="width: 100%; overflow-y: auto; overflow-x: hidden; "></iframe></div></div></div>');

and

       $('#iframe-i').height ( Math.min ( $(this).contents().find('html').height()+20, $(window).height()-20 ), true );

with

       $('#iframe-i').height ( Math.min ( $(this).contents().find('html').height()+20, $(window).height()-20 ), true );
  

I now get the following on Chrome and Edge.


The unnecessary horizontal scroll bar offends my sense of perfection but it’s a small price to pay to get something that then scrolls on my iPhone6 in both Safari and Chrome.


This is super cool!

I am using a custom-built Desktop PC running Windows 10. Using Chrome as my browser, I had absolutely no issues scrolling.

1 Like

On Safari I can scroll only after exiting full screen

Works fine for me on Firefox and Chrome on Linux, though notice the strange selected radio buttons on Firefox (doesn’t do that on Chrome):

What device?

Big Sur MacBook Air

1 Like

I’m using the following css for radio buttons:

	input[type=radio] { 
        	width: 35px; 
		height: 35px; 
	} 

It must be that Firefox doesn’t fully understand it.

I’ve come across this web page with a suggested solution.

overflow:scroll !important; -webkit-overflow-scrolling:touch !important;

However, before I try it – @arnon_weinberg , please could you talk me through why there are so many nested divs and which one this fix should probably be applied to?

<div id="iframe-o" style="visibility: hidden; position: relative; display: table; margin: auto;width:90%;overflow-x: hidden;">
<div id="iframe-m" style="display: table-cell; vertical-align: middle; overflow-y: auto; overflow-x: hidden;">
<div id="iframe-i" style="display: inline-block; width:100%; overflow-y: auto; overflow-x: hidden;">

My lack of understanding is why o and m are now more complicated than your original versions:

<div id="iframe-o" style="visibility: hidden; position: relative; display: table; margin: auto;">
<div id="iframe-m" style="display: table-cell; vertical-align: middle;">
<div id="iframe-i" style="display: inline-block; width:100%; overflow-y: auto; overflow-x: hidden;">

It’s all an elaborate scheme to give the content iframe some control over its own display.

Normally iframes inherit position and dimensions from their parent element, so if you embedded the iframe in a div with a certain width x height, and a certain position in the screen, then the iframe is stuck with that. The 3 nested divs implement the standard way to center block content both horizontally (margin: auto;) and vertically (vertical-align: middle;) using CSS (I am not aware of a reliable solution using a single div), prevent browser scrollbars (we only want scrollbars around the iframe), and follow the iframe content’s dimensions instead of the parent’s.

The vertical scrollbar is set in the inner (iframe-i) div (overflow-y: auto;) so my best guess is that the fix goes there.

1 Like

Please could you try it again now.

https://run.pavlovia.org/vespr/demographics/

I tried adding the new code to iframe-i but that gave a double scroll bar. Then I realised that I had scrolling in the iframe itself so I’ve added it there.

  $('body').append('<div id="iframe-o" style="visibility: hidden; position: relative; display: table; margin: auto;width:90%;overflow-x: hidden;"><div id="iframe-m" style="display: table-cell; vertical-align: middle; overflow-y: auto; overflow-x: hidden;"><div id="iframe-i" style="display: inline-block; width:100%; overflow-y: auto; overflow-x: hidden;"><iframe id="iframe" src="'+src+'" frameborder="0" style="width: 100%; overflow-y: auto; overflow-x: hidden;  overflow:scroll !important; -webkit-overflow-scrolling:touch !important;"></iframe></div></div></div>');

Earlier today I realised that I was getting an ugly double vertical scroll on my twin screens. Several hours and 40 commits later I think I’ve got there.

One key piece was to stop the resized html scrolling using

		document.getElementById('bodyForm').style = "width: " + xRes + "px; font-family: Arial, Helvetica, sans-serif; justify-content: left; overflow-x: hidden; overflow-y: hidden; background-color: grey; color: white; font-size: "+ yRes +"px";

The other was to realise that the scrolling div on my computer was the outer one.