But why?
I wanted to implement a design similar to the one below, where the button is always be positioned above the portal.
Solution
I wanted to experiment with a bruteforce solution to position the button dynamically based on the screen size.
To achieve this I:
- Collected datapoints for the optimal position on different screen sizes.
- Then trained a model based on these datapoints to predict optimal button position.
- Finally positioned the button based on the returned coordinates when the page load.
Step 1: Collect datapoints
To collect the datapoints I added a new target at the center of the button on the background:
Then I added a JavaScript function to log the coordinates of my mouse & the screen size on each click:
var inputs = [];
var outputs = [];
document.onmousedown = function(e) {
var x = e.pageX;
var y = e.pageY;
var h = screen.availHeight;
var w = screen.availWidth;
console.log('w:', w, 'h:', h, 'x:', x, 'y:', y);
inputs.push({w: w, h: h});
outputs.push({x: x, y: y});
}
Then I started clicking on the target while changing the screen size as shown in the video below:
Step 2: Find optimal button position
Once I collected enough datapoints I converted them to a list of inputs (w, h) & outputs (x, y):
> 'X = [' + inputs.map((e, i) => {return `(${e.w}, ${e.h})`;}).join(', ') + ']';
< X = [(2318, 1422), (2290, 1422), (2262, 1422), (2202, 1422), ...]
> 'Y = [' + outputs.map((e, i) => {return `(${e.x}, ${e.y})`;}).join(', ') + ']';
< Y = [(416, 210), (413, 207), (414, 210), (413, 212), (414, 208), ...]
Then I used Gradient boosting technique with Multi Output Regressor from sklearn to train a regression model on these points:
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.multioutput import MultiOutputRegressor
X = [...]
Y = [...]
model = MultiOutputRegressor(GradientBoostingRegressor(), n_jobs=-1)
model.fit(X, Y)
model.predict([[(2262, 1422)]])
Then I wrapped this model into a small Flask app that takes screen width & height as arguments and returns the optimal (x, y) coordinates for the button:
from flask import Flask, request, jsonify
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.multioutput import MultiOutputRegressor
app = Flask(__name__)
@app.route('/button_position')
def my_route():
w = request.args.get('w', default = 100, type = int)
h = request.args.get('h', default = 100, type = int)
X = [...]
Y = [...]
model = MultiOutputRegressor(GradientBoostingRegressor(), n_jobs=-1)
model.fit(X, Y)
proposed_position = model.predict([[w, h]])
return jsonify([proposed_position[0][0], proposed_position[0][1]])
Step 3: Position button on page load
Now that we have the model & api ready, we have to request the optimal button position from the api when loading the page and update the button position based on the coordinates that we get.
function position_apply_btn() {
var h = window.innerHeight;
var w = window.innerWidth;
fetch('http://127.0.0.1:5000/button_position?w=' + w + '&h=' + h)
.then(response => {
return response.json();
})
.then(json => {
let x = json[0];
let y = json[1];
set_position(x, y);
});
}
function set_position(x, y) {
e = document.getElementById('action-btn');
e.style.left = x - e.offsetWidth / 2 + 'px';
e.style.top = y - e.offsetHeight / 2 + 'px';
}
position_apply_btn();
Step 4: Result
Conclusion
I was surprised how well this turned out. I wonder if a similar but more optimized method (e.g. automatic screen resizes, optimal screen sizes …) can be used in a practical way to facilitate responsive web design?