Merge pull request #248 from matrix-org/matthew/fix-image-load-popping

Fix scroll offset popping around during image load by putting explicit height back on images
This commit is contained in:
Matthew Hodgson 2016-03-29 00:48:31 +01:00
commit e068a92cde
4 changed files with 59 additions and 21 deletions

View file

@ -16,6 +16,7 @@ limitations under the License.
var React = require('react');
var ReactDOM = require("react-dom");
var dis = require("../../dispatcher");
var sdk = require('../../index');
/* (almost) stateless UI component which builds the event tiles in the room timeline.
@ -301,8 +302,7 @@ module.exports = React.createClass({
ref={this._collectEventNode.bind(this, eventId)}
data-scroll-token={scrollToken}>
<EventTile mxEvent={mxEv} continuation={continuation}
last={last} isSelectedEvent={highlight}
onImageLoad={this._onImageLoad} />
last={last} isSelectedEvent={highlight} />
</li>
);
@ -368,14 +368,8 @@ module.exports = React.createClass({
this.eventNodes[eventId] = node;
},
// once images in the events load, make the scrollPanel check the
// scroll offsets.
_onImageLoad: function() {
var scrollPanel = this.refs.scrollPanel;
if (scrollPanel) {
scrollPanel.checkScroll();
}
onResize: function() {
dis.dispatch({ action: 'timeline_resize' }, true);
},
render: function() {
@ -392,6 +386,7 @@ module.exports = React.createClass({
return (
<ScrollPanel ref="scrollPanel" className="mx_RoomView_messagePanel"
onScroll={ this.props.onScroll }
onResize={ this.onResize }
onFillRequest={ this.props.onFillRequest }
style={ this.props.hidden ? { display: 'none' } : {} }
stickyBottom={ this.props.stickyBottom }>

View file

@ -485,6 +485,10 @@ module.exports = React.createClass({
}
},
onSearchResultsResize: function() {
dis.dispatch({ action: 'timeline_resize' }, true);
},
onSearchResultsFillRequest: function(backwards) {
if (!backwards)
return q(false);
@ -1361,7 +1365,7 @@ module.exports = React.createClass({
if (this.state.searchResults) {
searchResultsPanel = (
<ScrollPanel ref="searchResultsPanel" className="mx_RoomView_messagePanel mx_RoomView_searchResultsPanel"
onFillRequest={ this.onSearchResultsFillRequest }>
onFillRequest={ this.onSearchResultsFillRequest } onResize={ this.onSearchResultsResize }>
<li className={scrollheader_classes}></li>
{this.getSearchResultTiles()}
</ScrollPanel>

View file

@ -94,6 +94,11 @@ module.exports = React.createClass({
*/
onScroll: React.PropTypes.func,
/* onResize: a callback which is called whenever the Gemini scroll
* panel is resized
*/
onResize: React.PropTypes.func,
/* className: classnames to add to the top-level div
*/
className: React.PropTypes.string,
@ -181,6 +186,12 @@ module.exports = React.createClass({
this.checkFillState();
},
onResize: function() {
this.props.onResize();
this.checkScroll();
this.refs.geminiPanel.forceUpdate();
},
// after an update to the contents of the panel, check that the scroll is
// where it ought to be, and set off pagination requests if necessary.
checkScroll: function() {
@ -481,7 +492,7 @@ module.exports = React.createClass({
// reflect the fact that we don't necessarily contain a list of messages.
// it's not obvious why we have a separate div and ol anyway.
return (<GeminiScrollbar autoshow={true} ref="geminiPanel"
onScroll={this.onScroll} onResize={this.checkScroll}
onScroll={this.onScroll} onResize={this.onResize}
className={this.props.className} style={this.props.style}>
<div className="mx_RoomView_messageListWrapper">
<ol ref="itemlist" className="mx_RoomView_MessageList" aria-live="polite">

View file

@ -105,24 +105,52 @@ module.exports = React.createClass({
return MatrixClientPeg.get().mxcUrlToHttp(content.url, 800, 600);
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
this.fixupHeight();
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
},
onAction: function(payload) {
if (payload.action === "timeline_resize") {
this.fixupHeight();
}
},
fixupHeight: function() {
if (!this.refs.image) {
console.warn("Refusing to fix up height on MImageBody with no image element");
return;
}
var content = this.props.mxEvent.getContent();
var thumbHeight = null;
var timelineWidth = this.refs.body.offsetWidth;
var maxHeight = 600; // let images take up as much width as they can so long as the height doesn't exceed 600px.
// the alternative here would be 600*timelineWidth/800; to scale them down to fit inside a 4:3 bounding box
//console.log("trying to fit image into timelineWidth of " + this.refs.body.offsetWidth + " or " + this.refs.body.clientWidth);
if (content.info) thumbHeight = this.thumbHeight(content.info.w, content.info.h, timelineWidth, maxHeight);
this.refs.image.style.height = thumbHeight + "px";
// console.log("Image height now", thumbHeight);
},
render: function() {
var TintableSvg = sdk.getComponent("elements.TintableSvg");
var content = this.props.mxEvent.getContent();
var cli = MatrixClientPeg.get();
var thumbHeight = null;
if (content.info) thumbHeight = this.thumbHeight(content.info.w, content.info.h, 800, 600);
var imgStyle = {};
if (thumbHeight) imgStyle['maxHeight'] = thumbHeight;
var thumbUrl = this._getThumbUrl();
if (thumbUrl) {
return (
<span className="mx_MImageBody">
<span className="mx_MImageBody" ref="body">
<a href={cli.mxcUrlToHttp(content.url)} onClick={ this.onClick }>
<img className="mx_MImageBody_thumbnail" src={thumbUrl}
alt={content.body} style={imgStyle}
<img className="mx_MImageBody_thumbnail" src={thumbUrl} ref="image"
alt={content.body}
onMouseEnter={this.onImageEnter}
onMouseLeave={this.onImageLeave}
onLoad={this.props.onImageLoad} />