Total change of DOM layout, scrolling only on main, needs testing
This commit is contained in:
@ -2665,59 +2665,39 @@
|
||||
|
||||
body,
|
||||
html {
|
||||
background: #181818;
|
||||
color: #cecac4;
|
||||
font-family: "Overpass", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
letter-spacing: -0.01rem;
|
||||
touch-action: manipulation;
|
||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004); }
|
||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden; }
|
||||
body .light-theme,
|
||||
html .light-theme {
|
||||
background: #FFFFFF;
|
||||
color: #121212; }
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px; }
|
||||
body.touch-dragging, body.no-scrollbars {
|
||||
overflow-y: hidden; }
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
background: #181818; }
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
-webkit-transition: all 0.2s ease-in-out;
|
||||
-moz-transition: all 0.2s ease-in-out;
|
||||
-o-transition: all 0.2s ease-in-out;
|
||||
transition: all 0.2s ease-in-out;
|
||||
background: #383734;
|
||||
border-radius: 4px; }
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background: #a3a19f; }
|
||||
|
||||
body {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden; }
|
||||
body.touch-dragging, body.no-scrollbars {
|
||||
overflow-y: hidden; }
|
||||
@media (max-width: 800px) {
|
||||
body.context-menu-open {
|
||||
overflow-y: hidden; } }
|
||||
|
||||
main,
|
||||
footer {
|
||||
margin-left: 220px; }
|
||||
@media (max-height: 650px) {
|
||||
main,
|
||||
footer {
|
||||
padding-bottom: 60px; } }
|
||||
@media (max-width: 800px) {
|
||||
main,
|
||||
footer {
|
||||
margin-left: 0; } }
|
||||
@media (max-width: 800px) {
|
||||
body.context-menu-open {
|
||||
overflow-y: hidden; } }
|
||||
|
||||
main {
|
||||
position: relative;
|
||||
min-height: 100vh; }
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 220px;
|
||||
bottom: 50px;
|
||||
right: 0;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
perspective: 2px;
|
||||
transform-style: preserve-3d;
|
||||
background: #181818; }
|
||||
.light-theme main {
|
||||
background: #FFFFFF; }
|
||||
main .content-wrapper {
|
||||
padding: 40px 40px 80px; }
|
||||
main a {
|
||||
@ -2733,10 +2713,12 @@ main {
|
||||
line-height: 1.5em;
|
||||
white-space: pre-wrap; }
|
||||
@media (max-width: 800px) {
|
||||
main .content-wrapper {
|
||||
padding: 20px 10px 60px; }
|
||||
main section.list-wrapper {
|
||||
padding: 10px 0; } }
|
||||
main {
|
||||
left: 0; }
|
||||
main .content-wrapper {
|
||||
padding: 20px 10px 60px; }
|
||||
main section.list-wrapper {
|
||||
padding: 10px 0; } }
|
||||
main pre {
|
||||
padding: 20px;
|
||||
background: #121212;
|
||||
@ -4364,35 +4346,21 @@ input[type="submit"] {
|
||||
.thumbnail:hover .zoom {
|
||||
display: block; }
|
||||
|
||||
html,
|
||||
body {
|
||||
overflow: hidden; }
|
||||
|
||||
main {
|
||||
perspective: 2px;
|
||||
transform-style: preserve-3d;
|
||||
overflow-y: scroll;
|
||||
height: 100vh;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0; }
|
||||
|
||||
.intro {
|
||||
overflow: hidden; }
|
||||
.playback-controls {
|
||||
z-index: 3; }
|
||||
|
||||
.parallax {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: #181818;
|
||||
position: absolute;
|
||||
top: -30%;
|
||||
top: -10%;
|
||||
right: -10px;
|
||||
bottom: -10px;
|
||||
left: -10px;
|
||||
transform: translateZ(-2px) scale(2);
|
||||
z-index: -1; }
|
||||
height: 100%;
|
||||
transform: translateZ(-0.6px) scale(1.33334);
|
||||
transform-origin: bottom center;
|
||||
z-index: -1;
|
||||
max-height: 80vh; }
|
||||
.parallax__image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -4401,32 +4369,27 @@ main {
|
||||
right: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: 50% 50%; }
|
||||
background-position: 50% 50%;
|
||||
background-color: #323232; }
|
||||
.parallax__overlay {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 30%;
|
||||
height: 100%;
|
||||
background-image: linear-gradient(rgba(24, 24, 24, 0), #181818);
|
||||
z-index: 1;
|
||||
transform: translateZ(2px) scale(0.5); }
|
||||
.parallax canvas {
|
||||
width: 110%;
|
||||
height: 110%;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: -5%;
|
||||
left: -5%; }
|
||||
.parallax canvas.loaded {
|
||||
opacity: 1; }
|
||||
.parallax.blur canvas {
|
||||
z-index: 1; }
|
||||
.parallax--blur .parallax__image {
|
||||
-webkit-filter: blur(10px);
|
||||
filter: blur(10px); }
|
||||
.light-theme .parallax {
|
||||
background: #FFFFFF; }
|
||||
.light-theme .parallax__image {
|
||||
background-color: #FFFFFF; }
|
||||
.light-theme .parallax__overlay {
|
||||
background-image: linear-gradient(rgba(255, 255, 255, 0), white); }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
@ -7078,14 +7041,6 @@ main .track-view {
|
||||
.discover-featured-view .intro header,
|
||||
.discover-new-releases-view .intro header {
|
||||
margin-bottom: 0; }
|
||||
.discover-featured-view .intro .parallax,
|
||||
.discover-new-releases-view .intro .parallax {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1; }
|
||||
.discover-featured-view .intro .content,
|
||||
.discover-new-releases-view .intro .content {
|
||||
padding: 40px;
|
||||
|
||||
@ -18781,6 +18781,7 @@ var LazyLoadListener = function (_React$Component) {
|
||||
loadKey: _this.props.loadKey
|
||||
};
|
||||
|
||||
_this.element = document.getElementById('main');
|
||||
_this.handleScroll = helpers.throttle(_this.handleScroll.bind(_this), 50);
|
||||
return _this;
|
||||
}
|
||||
@ -18788,12 +18789,12 @@ var LazyLoadListener = function (_React$Component) {
|
||||
_createClass(LazyLoadListener, [{
|
||||
key: 'componentWillMount',
|
||||
value: function componentWillMount() {
|
||||
window.addEventListener("scroll", this.handleScroll, false);
|
||||
this.element.addEventListener("scroll", this.handleScroll, false);
|
||||
}
|
||||
}, {
|
||||
key: 'componentWillUnmount',
|
||||
value: function componentWillUnmount() {
|
||||
window.removeEventListener("scroll", this.handleScroll, false);
|
||||
this.element.removeEventListener("scroll", this.handleScroll, false);
|
||||
}
|
||||
}, {
|
||||
key: 'componentWillReceiveProps',
|
||||
@ -18813,7 +18814,7 @@ var LazyLoadListener = function (_React$Component) {
|
||||
if (this.state.listening) {
|
||||
|
||||
// At, or nearly at the bottom of the page
|
||||
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 80) {
|
||||
if (this.element.scrollTop > this.element.scrollHeight - this.element.offsetHeight - 100) {
|
||||
|
||||
// Immediately stop listening to avoid duplicating pagination requests
|
||||
this.setState({ listening: false }, function () {
|
||||
@ -19880,218 +19881,19 @@ var Parallax = function (_React$Component) {
|
||||
_classCallCheck(this, Parallax);
|
||||
|
||||
return _possibleConstructorReturn(this, (Parallax.__proto__ || Object.getPrototypeOf(Parallax)).call(this, props));
|
||||
/*
|
||||
this._loading = false
|
||||
this._loaded = false
|
||||
|
||||
this.state = {
|
||||
scrollTop: 0,
|
||||
windowWidth: 0,
|
||||
windowHeight: 0,
|
||||
canvas: {
|
||||
width: 0,
|
||||
height: 0
|
||||
},
|
||||
image: {},
|
||||
loading: false,
|
||||
url: false
|
||||
}
|
||||
|
||||
if (!this.props.disabled){
|
||||
|
||||
// we need to manually bind this as eventListener doesn't work with anonymous functions
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
this.handleScroll = this.handleScroll.bind(this);
|
||||
}*/
|
||||
}
|
||||
/*
|
||||
componentDidMount(){
|
||||
if (!this.props.disabled){
|
||||
window.addEventListener("resize", this.handleResize);
|
||||
window.addEventListener("scroll", this.handleScroll);
|
||||
}
|
||||
|
||||
this._mounted = true;
|
||||
|
||||
if (this.props.image){
|
||||
this._loading = true;
|
||||
this.setState({url: this.props.image, image: false, loading: true});
|
||||
this.loadImage(this.props.image)
|
||||
.then(
|
||||
response => {
|
||||
if (this._mounted){
|
||||
this._loading = false;
|
||||
this._loaded = true;
|
||||
this.setState({url: this.props.image, image: response, loading: false});
|
||||
this.updateCanvas(response);
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
this._loaded = true;
|
||||
this.state.image = false;
|
||||
this.updateCanvas();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
this._mounted = false;
|
||||
|
||||
if (!this.props.disabled){
|
||||
window.removeEventListener("resize", this.handleResize);
|
||||
window.removeEventListener("scroll", this.handleScroll);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps){
|
||||
if ((!this.state.url || nextProps.image != this.state.url ) && !this._loading && nextProps.image){
|
||||
this._loading = true;
|
||||
this.setState({ url: nextProps.image, image: false, loading: true });
|
||||
this.loadImage(nextProps.image)
|
||||
.then(
|
||||
response => {
|
||||
if (this._mounted){
|
||||
this._loading = false;
|
||||
this._loaded = true;
|
||||
this.setState({ url: nextProps.image, image: response, loading: false });
|
||||
this.updateCanvas(response);
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
handleResize(e){
|
||||
this.updateCanvas(this.state.image);
|
||||
}
|
||||
|
||||
handleScroll(e){
|
||||
// this DOES work, but is in no way high-performing and only on Firefox
|
||||
if (this._loaded){
|
||||
this.setState(
|
||||
{scrollTop: window.scrollY},
|
||||
this.updateCanvas(this.state.image)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
loadImage(url){
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
var imageObject = new Image();
|
||||
imageObject.src = url;
|
||||
|
||||
// This seems to prevent Dirble images from loading.
|
||||
// Other domains don't seem to mind any more, so perhaps we can remove for good.
|
||||
// TODO: Re-test the nature of crossorigin images
|
||||
// imageObject.crossOrigin = 'anonymous';
|
||||
|
||||
imageObject.onload = function(){
|
||||
var image = {
|
||||
width: imageObject.naturalWidth,
|
||||
height: imageObject.naturalHeight,
|
||||
original_width: imageObject.naturalWidth,
|
||||
original_height: imageObject.naturalHeight,
|
||||
object: imageObject
|
||||
}
|
||||
resolve(image);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
updateCanvas(image = false){
|
||||
var canvasWidth = $('.parallax').outerWidth();
|
||||
var canvasHeight = $('.parallax').outerHeight();
|
||||
if (this.state.canvas.width != canvasWidth || this.state.canvas.height != canvasHeight){
|
||||
this.setState({
|
||||
canvas: {
|
||||
width: canvasWidth,
|
||||
height: canvasHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
this.renderCanvas(image);
|
||||
}
|
||||
|
||||
renderCanvas(image = false){
|
||||
let self = this;
|
||||
var canvasDOM = document.getElementById('parallax-canvas')
|
||||
var context = canvasDOM.getContext('2d')
|
||||
|
||||
// Fill the background with mid-grey
|
||||
context.beginPath();
|
||||
context.rect(0, 0, this.state.canvas.width, this.state.canvas.height);
|
||||
|
||||
switch (this.props.theme){
|
||||
case 'light':
|
||||
context.fillStyle = "#EDEDED";
|
||||
break;
|
||||
|
||||
case 'dark':
|
||||
default:
|
||||
context.fillStyle = "#121212";
|
||||
}
|
||||
context.fill();
|
||||
|
||||
if (image && !self.state.loading){
|
||||
|
||||
// zoom image to fill canvas, widthwise
|
||||
if (image.width < this.state.canvas.width || image.width > this.state.canvas.width){
|
||||
var scale = this.state.canvas.width / image.width;
|
||||
image.width = image.width * scale;
|
||||
image.height = image.height * scale;
|
||||
}
|
||||
|
||||
// now check for fill heightwise, and zoom in if necessary
|
||||
if (image.height < this.state.canvas.height){
|
||||
var scale = this.state.canvas.height / image.height;
|
||||
image.width = image.width * scale;
|
||||
image.height = image.height * scale;
|
||||
}
|
||||
|
||||
// figure out where we want the image to be, based on scroll position
|
||||
var percent = Math.round(self.state.scrollTop / this.state.canvas.height * 100 );
|
||||
var position = Math.round((this.state.canvas.height / 2) * (percent/100) ) - 100;
|
||||
|
||||
image.x = (this.state.canvas.width / 2 ) - (image.width / 2 );
|
||||
image.y = ((this.state.canvas.height / 2 ) - (image.height / 2 ) ) + ((percent / 100 ) * 100);
|
||||
|
||||
// Actually draw the image on the canvas
|
||||
context.drawImage(image.object, image.x, image.y, image.width, image.height);
|
||||
|
||||
// now update our component
|
||||
self.setState({ image: image });
|
||||
}
|
||||
|
||||
// Construct a gradient overlay
|
||||
context.rect(0, 0, this.state.canvas.width, this.state.canvas.height);
|
||||
let gradient = context.createLinearGradient(0, 0, 0, this.state.canvas.height);
|
||||
|
||||
switch (this.props.theme){
|
||||
case 'light':
|
||||
gradient.addColorStop(0, 'rgba(255,255,255,0.4)');
|
||||
gradient.addColorStop(0.9, 'rgba(255,255,255,1)');
|
||||
break;
|
||||
|
||||
case 'dark':
|
||||
default:
|
||||
gradient.addColorStop(0, 'rgba(24,24,24,0)');
|
||||
gradient.addColorStop(0.9, 'rgba(24,24,24,1)');
|
||||
}
|
||||
|
||||
context.fillStyle = gradient;
|
||||
|
||||
// And now drop it into place
|
||||
context.fill();
|
||||
}
|
||||
*/
|
||||
|
||||
_createClass(Parallax, [{
|
||||
key: "render",
|
||||
value: function render() {
|
||||
var class_name = "parallax";
|
||||
if (this.props.blur) {
|
||||
class_name += " parallax--blur";
|
||||
}
|
||||
|
||||
return _react2.default.createElement(
|
||||
"div",
|
||||
{ className: this.props.blur && !this.props.disabled ? "parallax blur" : "parallax" },
|
||||
{ className: class_name },
|
||||
_react2.default.createElement("div", { className: "parallax__image", style: { backgroundImage: 'url(' + this.props.image + ')' } }),
|
||||
_react2.default.createElement("div", { className: "parallax__overlay" })
|
||||
);
|
||||
@ -61644,7 +61446,7 @@ var App = function (_React$Component) {
|
||||
_react2.default.createElement(_PlaybackControls2.default, null),
|
||||
_react2.default.createElement(
|
||||
'main',
|
||||
null,
|
||||
{ id: 'main' },
|
||||
this.props.children
|
||||
)
|
||||
),
|
||||
@ -67911,7 +67713,7 @@ var Queue = function (_React$Component) {
|
||||
_react2.default.createElement(_Icon2.default, { name: 'play_arrow', type: 'material' }),
|
||||
'Now playing'
|
||||
),
|
||||
_react2.default.createElement(_Parallax2.default, { blur: true, image: current_track_image, theme: this.props.theme, disabled: this.props.disable_parallax }),
|
||||
_react2.default.createElement(_Parallax2.default, { blur: true, image: current_track_image }),
|
||||
_react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'content-wrapper' },
|
||||
@ -67972,7 +67774,6 @@ var Queue = function (_React$Component) {
|
||||
|
||||
var mapStateToProps = function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
disable_parallax: state.ui.disable_parallax,
|
||||
theme: state.ui.theme,
|
||||
spotify_enabled: state.spotify.enabled,
|
||||
radio: state.core.radio,
|
||||
@ -70042,27 +69843,6 @@ var Settings = function (_React$Component) {
|
||||
{ className: 'label' },
|
||||
'Enable shortkeys'
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'label',
|
||||
null,
|
||||
_react2.default.createElement('input', {
|
||||
type: 'checkbox',
|
||||
name: 'shortkeys_enabled',
|
||||
checked: this.props.ui.disable_parallax,
|
||||
onChange: function onChange(e) {
|
||||
return _this3.props.uiActions.set({ disable_parallax: !_this3.props.ui.disable_parallax });
|
||||
} }),
|
||||
_react2.default.createElement(
|
||||
'span',
|
||||
{ className: 'label tooltip' },
|
||||
'Disable parallax',
|
||||
_react2.default.createElement(
|
||||
'span',
|
||||
{ className: 'tooltip__content' },
|
||||
'Improves scroll performance on low-powered devices'
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
@ -77898,7 +77678,7 @@ var DiscoverNewReleases = function (_React$Component) {
|
||||
return _react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'intro' },
|
||||
_react2.default.createElement(_Parallax2.default, { image: album.images ? album.images.large : null, blur: true, theme: this.props.theme, disabled: this.props.disable_parallax }),
|
||||
_react2.default.createElement(_Parallax2.default, { image: album.images ? album.images.large : null, blur: true }),
|
||||
_react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'content cf' },
|
||||
@ -78051,7 +77831,6 @@ var DiscoverNewReleases = function (_React$Component) {
|
||||
|
||||
var mapStateToProps = function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
disable_parallax: state.ui.disable_parallax,
|
||||
theme: state.ui.theme,
|
||||
load_queue: state.ui.load_queue,
|
||||
artists: state.core.artists,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -321,7 +321,7 @@ class App extends React.Component{
|
||||
<div className="body">
|
||||
<Sidebar />
|
||||
<PlaybackControls />
|
||||
<main>
|
||||
<main id="main">
|
||||
{this.props.children}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@ -11,15 +11,16 @@ export default class LazyLoadListener extends React.Component{
|
||||
loadKey: this.props.loadKey
|
||||
}
|
||||
|
||||
this.element = document.getElementById('main');
|
||||
this.handleScroll = helpers.throttle(this.handleScroll.bind(this), 50);
|
||||
}
|
||||
|
||||
componentWillMount(){
|
||||
window.addEventListener("scroll", this.handleScroll, false);
|
||||
this.element.addEventListener("scroll", this.handleScroll, false);
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
window.removeEventListener("scroll", this.handleScroll, false);
|
||||
this.element.removeEventListener("scroll", this.handleScroll, false);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps){
|
||||
@ -35,7 +36,7 @@ export default class LazyLoadListener extends React.Component{
|
||||
if (this.state.listening){
|
||||
|
||||
// At, or nearly at the bottom of the page
|
||||
if ((window.innerHeight + window.scrollY) >= (document.body.offsetHeight - 80)){
|
||||
if (this.element.scrollTop > (this.element.scrollHeight - this.element.offsetHeight - 100)){
|
||||
|
||||
// Immediately stop listening to avoid duplicating pagination requests
|
||||
this.setState(
|
||||
|
||||
@ -5,215 +5,16 @@ export default class Parallax extends React.Component{
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
/*
|
||||
this._loading = false
|
||||
this._loaded = false
|
||||
|
||||
this.state = {
|
||||
scrollTop: 0,
|
||||
windowWidth: 0,
|
||||
windowHeight: 0,
|
||||
canvas: {
|
||||
width: 0,
|
||||
height: 0
|
||||
},
|
||||
image: {},
|
||||
loading: false,
|
||||
url: false
|
||||
}
|
||||
|
||||
if (!this.props.disabled){
|
||||
|
||||
// we need to manually bind this as eventListener doesn't work with anonymous functions
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
this.handleScroll = this.handleScroll.bind(this);
|
||||
}*/
|
||||
}
|
||||
/*
|
||||
componentDidMount(){
|
||||
if (!this.props.disabled){
|
||||
window.addEventListener("resize", this.handleResize);
|
||||
window.addEventListener("scroll", this.handleScroll);
|
||||
}
|
||||
|
||||
this._mounted = true;
|
||||
|
||||
if (this.props.image){
|
||||
this._loading = true;
|
||||
this.setState({url: this.props.image, image: false, loading: true});
|
||||
this.loadImage(this.props.image)
|
||||
.then(
|
||||
response => {
|
||||
if (this._mounted){
|
||||
this._loading = false;
|
||||
this._loaded = true;
|
||||
this.setState({url: this.props.image, image: response, loading: false});
|
||||
this.updateCanvas(response);
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
this._loaded = true;
|
||||
this.state.image = false;
|
||||
this.updateCanvas();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
this._mounted = false;
|
||||
|
||||
if (!this.props.disabled){
|
||||
window.removeEventListener("resize", this.handleResize);
|
||||
window.removeEventListener("scroll", this.handleScroll);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps){
|
||||
if ((!this.state.url || nextProps.image != this.state.url ) && !this._loading && nextProps.image){
|
||||
this._loading = true;
|
||||
this.setState({ url: nextProps.image, image: false, loading: true });
|
||||
this.loadImage(nextProps.image)
|
||||
.then(
|
||||
response => {
|
||||
if (this._mounted){
|
||||
this._loading = false;
|
||||
this._loaded = true;
|
||||
this.setState({ url: nextProps.image, image: response, loading: false });
|
||||
this.updateCanvas(response);
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
handleResize(e){
|
||||
this.updateCanvas(this.state.image);
|
||||
}
|
||||
|
||||
handleScroll(e){
|
||||
// this DOES work, but is in no way high-performing and only on Firefox
|
||||
if (this._loaded){
|
||||
this.setState(
|
||||
{scrollTop: window.scrollY},
|
||||
this.updateCanvas(this.state.image)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
loadImage(url){
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
var imageObject = new Image();
|
||||
imageObject.src = url;
|
||||
|
||||
// This seems to prevent Dirble images from loading.
|
||||
// Other domains don't seem to mind any more, so perhaps we can remove for good.
|
||||
// TODO: Re-test the nature of crossorigin images
|
||||
// imageObject.crossOrigin = 'anonymous';
|
||||
|
||||
imageObject.onload = function(){
|
||||
var image = {
|
||||
width: imageObject.naturalWidth,
|
||||
height: imageObject.naturalHeight,
|
||||
original_width: imageObject.naturalWidth,
|
||||
original_height: imageObject.naturalHeight,
|
||||
object: imageObject
|
||||
}
|
||||
resolve(image);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
updateCanvas(image = false){
|
||||
var canvasWidth = $('.parallax').outerWidth();
|
||||
var canvasHeight = $('.parallax').outerHeight();
|
||||
if (this.state.canvas.width != canvasWidth || this.state.canvas.height != canvasHeight){
|
||||
this.setState({
|
||||
canvas: {
|
||||
width: canvasWidth,
|
||||
height: canvasHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
this.renderCanvas(image);
|
||||
}
|
||||
|
||||
renderCanvas(image = false){
|
||||
let self = this;
|
||||
var canvasDOM = document.getElementById('parallax-canvas')
|
||||
var context = canvasDOM.getContext('2d')
|
||||
|
||||
// Fill the background with mid-grey
|
||||
context.beginPath();
|
||||
context.rect(0, 0, this.state.canvas.width, this.state.canvas.height);
|
||||
|
||||
switch (this.props.theme){
|
||||
case 'light':
|
||||
context.fillStyle = "#EDEDED";
|
||||
break;
|
||||
|
||||
case 'dark':
|
||||
default:
|
||||
context.fillStyle = "#121212";
|
||||
}
|
||||
context.fill();
|
||||
|
||||
if (image && !self.state.loading){
|
||||
|
||||
// zoom image to fill canvas, widthwise
|
||||
if (image.width < this.state.canvas.width || image.width > this.state.canvas.width){
|
||||
var scale = this.state.canvas.width / image.width;
|
||||
image.width = image.width * scale;
|
||||
image.height = image.height * scale;
|
||||
}
|
||||
|
||||
// now check for fill heightwise, and zoom in if necessary
|
||||
if (image.height < this.state.canvas.height){
|
||||
var scale = this.state.canvas.height / image.height;
|
||||
image.width = image.width * scale;
|
||||
image.height = image.height * scale;
|
||||
}
|
||||
|
||||
// figure out where we want the image to be, based on scroll position
|
||||
var percent = Math.round(self.state.scrollTop / this.state.canvas.height * 100 );
|
||||
var position = Math.round((this.state.canvas.height / 2) * (percent/100) ) - 100;
|
||||
|
||||
image.x = (this.state.canvas.width / 2 ) - (image.width / 2 );
|
||||
image.y = ((this.state.canvas.height / 2 ) - (image.height / 2 ) ) + ((percent / 100 ) * 100);
|
||||
|
||||
// Actually draw the image on the canvas
|
||||
context.drawImage(image.object, image.x, image.y, image.width, image.height);
|
||||
|
||||
// now update our component
|
||||
self.setState({ image: image });
|
||||
}
|
||||
|
||||
// Construct a gradient overlay
|
||||
context.rect(0, 0, this.state.canvas.width, this.state.canvas.height);
|
||||
let gradient = context.createLinearGradient(0, 0, 0, this.state.canvas.height);
|
||||
|
||||
switch (this.props.theme){
|
||||
case 'light':
|
||||
gradient.addColorStop(0, 'rgba(255,255,255,0.4)');
|
||||
gradient.addColorStop(0.9, 'rgba(255,255,255,1)');
|
||||
break;
|
||||
|
||||
case 'dark':
|
||||
default:
|
||||
gradient.addColorStop(0, 'rgba(24,24,24,0)');
|
||||
gradient.addColorStop(0.9, 'rgba(24,24,24,1)');
|
||||
}
|
||||
|
||||
context.fillStyle = gradient;
|
||||
|
||||
// And now drop it into place
|
||||
context.fill();
|
||||
}
|
||||
*/
|
||||
|
||||
render(){
|
||||
var class_name = "parallax";
|
||||
if (this.props.blur){
|
||||
class_name += " parallax--blur";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={this.props.blur && !this.props.disabled ? "parallax blur" : "parallax"}>
|
||||
<div className={class_name}>
|
||||
<div className="parallax__image" style={{backgroundImage: 'url('+this.props.image+')'}}></div>
|
||||
<div className="parallax__overlay"></div>
|
||||
</div>
|
||||
|
||||
@ -165,7 +165,7 @@ class Queue extends React.Component{
|
||||
<Icon name="play_arrow" type="material" />
|
||||
Now playing
|
||||
</Header>
|
||||
<Parallax blur image={current_track_image} theme={this.props.theme} disabled={this.props.disable_parallax} />
|
||||
<Parallax blur image={current_track_image} />
|
||||
<div className="content-wrapper">
|
||||
|
||||
<div className="current-track">
|
||||
@ -204,7 +204,6 @@ class Queue extends React.Component{
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
disable_parallax: state.ui.disable_parallax,
|
||||
theme: state.ui.theme,
|
||||
spotify_enabled: state.spotify.enabled,
|
||||
radio: state.core.radio,
|
||||
|
||||
@ -330,17 +330,6 @@ class Settings extends React.Component {
|
||||
Enable shortkeys
|
||||
</span>
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="shortkeys_enabled"
|
||||
checked={ this.props.ui.disable_parallax }
|
||||
onChange={ e => this.props.uiActions.set({ disable_parallax: !this.props.ui.disable_parallax })} />
|
||||
<span className="label tooltip">
|
||||
Disable parallax
|
||||
<span className="tooltip__content">Improves scroll performance on low-powered devices</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -60,7 +60,7 @@ class DiscoverNewReleases extends React.Component{
|
||||
if (album){
|
||||
return (
|
||||
<div className="intro">
|
||||
<Parallax image={album.images ? album.images.large : null} blur theme={this.props.theme} disabled={this.props.disable_parallax} />
|
||||
<Parallax image={album.images ? album.images.large : null} blur />
|
||||
<div className="content cf">
|
||||
<Link
|
||||
to={global.baseURL+'album/'+album.uri}
|
||||
@ -155,7 +155,6 @@ class DiscoverNewReleases extends React.Component{
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
disable_parallax: state.ui.disable_parallax,
|
||||
theme: state.ui.theme,
|
||||
load_queue: state.ui.load_queue,
|
||||
artists: state.core.artists,
|
||||
|
||||
@ -63,38 +63,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
main {
|
||||
perspective: 2px;
|
||||
transform-style: preserve-3d;
|
||||
overflow-y: scroll;
|
||||
height: 100vh;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.intro {
|
||||
overflow: hidden;
|
||||
.playback-controls {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.parallax {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: colour(dark_grey);
|
||||
position: absolute;
|
||||
top: -30%;
|
||||
top: -10%;
|
||||
right: -10px;
|
||||
bottom: -10px;
|
||||
left: -10px;
|
||||
transform: translateZ(-2px) scale(2);
|
||||
height: 100%;
|
||||
transform: translateZ(-0.6px) scale(1.33334);
|
||||
transform-origin: bottom center;
|
||||
z-index: -1;
|
||||
max-height: 80vh;
|
||||
|
||||
&__image {
|
||||
position: absolute;
|
||||
@ -105,41 +89,36 @@ main {
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: 50% 50%;
|
||||
background-color: lighten(colour(dark_grey), 10%);
|
||||
}
|
||||
|
||||
&__overlay {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 30%;
|
||||
height: 100%;
|
||||
background-image: linear-gradient(rgba(24,24,24,0), rgba(24,24,24,1));
|
||||
z-index: 1;
|
||||
transform: translateZ(2px) scale(0.5);
|
||||
}
|
||||
|
||||
canvas {
|
||||
width: 110%;
|
||||
height: 110%;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: -5%;
|
||||
left: -5%;
|
||||
|
||||
&.loaded {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.blur {
|
||||
canvas {
|
||||
&--blur {
|
||||
.parallax__image {
|
||||
@include blur();
|
||||
}
|
||||
}
|
||||
|
||||
.light-theme & {
|
||||
background: colour(white);
|
||||
|
||||
&__image {
|
||||
background-color: colour(white);
|
||||
}
|
||||
|
||||
&__overlay {
|
||||
background-image: linear-gradient(rgba(255,255,255,0), rgba(255,255,255,1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,42 +33,22 @@
|
||||
|
||||
body,
|
||||
html {
|
||||
background: colour(dark_grey);
|
||||
color: colour(light_grey);
|
||||
font-family: "Overpass", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
letter-spacing: -0.01rem;
|
||||
touch-action: manipulation;
|
||||
text-shadow: 1px 1px 1px rgba(0,0,0,0.004);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.light-theme {
|
||||
background: colour(white);
|
||||
color: colour(darkest_grey);
|
||||
}
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar{
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
background: colour(dark_grey);
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
@include animate();
|
||||
background: colour(grey);
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: colour(mid_grey);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
|
||||
&.touch-dragging,
|
||||
&.no-scrollbars {
|
||||
@ -82,22 +62,21 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
main,
|
||||
footer {
|
||||
margin-left: 220px;
|
||||
|
||||
@include responsive(null, null, $bp_shallow){
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
@include responsive($bp_medium){
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 220px;
|
||||
bottom: 50px;
|
||||
right: 0;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
perspective: 2px;
|
||||
transform-style: preserve-3d;
|
||||
background: colour(dark_grey);
|
||||
|
||||
.light-theme & {
|
||||
background: colour(white);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 40px 40px 80px;
|
||||
@ -124,6 +103,7 @@ main {
|
||||
}
|
||||
|
||||
@include responsive($bp_medium){
|
||||
left: 0;
|
||||
|
||||
.content-wrapper {
|
||||
padding: 20px 10px 60px;
|
||||
|
||||
@ -237,8 +237,8 @@ $bp_shallow: 650px;
|
||||
}
|
||||
|
||||
@mixin blur($size: 10px){
|
||||
-webkit-filter: blur($size );
|
||||
filter: blur($size );
|
||||
-webkit-filter: blur($size);
|
||||
filter: blur($size);
|
||||
}
|
||||
|
||||
@mixin feature_font(){
|
||||
|
||||
@ -262,15 +262,6 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.parallax {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 40px;
|
||||
color: colour(white);
|
||||
|
||||
Reference in New Issue
Block a user