From 992bd6369f45b38e54992405e841b8711f8f4cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Diemer?= Date: Tue, 14 Nov 2023 15:49:50 +0100 Subject: [PATCH] Fix reflects transparency | refs #38802 --- dist/cover-list.min.js | 2 +- src/cover-list.js | 97 +++++++++++++++++++++++------------------- test/index.html | 48 +++++++++++++-------- 3 files changed, 85 insertions(+), 62 deletions(-) diff --git a/dist/cover-list.min.js b/dist/cover-list.min.js index ab202f0..18bda06 100644 --- a/dist/cover-list.min.js +++ b/dist/cover-list.min.js @@ -1 +1 @@ -function CoverCanvasBox(t){this.FIELDS=["padding","x","y","w","h","z"],this.id=0,this.bw=240,this.bh=135,this.padding=3,this.title="",this.titleH=45,this.x=0,this.y=0,this.w=1,this.h=1,this.z=0,this.target={x:0,xStep:0,y:0,yStep:0,w:1,wStep:0,h:1,hStep:0,z:0,zStep:0},this.color="#666",this.boxBg="#fff",this.thumb="",this.url="",this.callback=null;for(const i in t)this[i]=t[i];this.canvas=null}function CoverList(t){if(this.widgetPlace="#cover_list",this.yOffset=-10,this.padding=3,this.boxWidth=240+2*this.padding,this.boxHeight=135+2*this.padding,this.minSize=.8,this.selected=-1,this.color="#666",this.boxBg="#fff",this.widgetElement=null,this.widgetWidth=0,this.widgetHeight=0,this.rangeInput=null,this.elements=[],this.positions=[],this.animation={duration:200,interval:25,currentTime:0},jsu.setObjectAttributes(this,t,["widgetPlace","padding","boxWidth","boxHeight","minSize","selected","color","boxBg"]),t&&t.elements)for(let i=0;ie&&(h=e,n=e/i),n>s&&(n=s,h=s*i),o.drawImage(t,this.padding+Math.floor((e-h)/2),this.padding+Math.floor((s-n)/2),h,n)}if(this.title){o.fillStyle="rgba(0, 0, 0, 0.7)",o.fillRect(this.padding,this.padding+s-this.titleH,e,this.titleH);const t=16;o.font="italic "+t+"px Arial",o.fillStyle="#fff",o.textAlign="center",o.textBaseline="middle";const i=this.padding+Math.floor(e/2);let h;const n=e-10;let a="",l="",r=!0;if(o.measureText(this.title).width>n){const t=this.title.split(" ");let i=t.length;for(let e=0;en){if(!a)for(let t=0;tn-20){a+=" ...",r=!1;break}a+=i[t]}break}a+=" "+i,t.shift()}if(r){i=t.length;for(let e=0;en-20){if(l)l+=" ...";else for(let t=0;tn-20){l+=" ...";break}l+=i[t]}break}l+=" "+i,t.shift()}}}else a=this.title;a&&l?(h=this.padding+s-Math.floor((this.titleH+t)/2),o.fillText(a,i,h),h+=4+t,o.fillText(l,i,h)):(h=this.padding+s-Math.floor(this.titleH/2),o.fillText(a,i,h))}o.save(),null!=this.callback&&this.callback(this.id,!1)},CoverCanvasBox.prototype.setTarget=function(t){const i=t.steps?t.steps:50;for(let e=0;e=this.x&&t<=this.x+this.w&&i>=this.y&&i<=this.y+this.h},CoverList.prototype.addElement=function(t){const i={index:this.elements.length,title:"No title",thumb:"",url:""};for(const e in t)i[e]=t[e];this.elements.push(i)},CoverList.prototype.initCoverList=function(){this.selected<0?this.selected=Math.floor(this.elements.length/2):this.selected>=this.elements.length&&(this.selected=this.elements.length-1);let t="";for(const i of this.elements)t+='
  • '+jsu.escapeHTML(i.title)+"
  • ";const i='
      '+t+'
    ';this.widgetElement=document.querySelector(this.widgetPlace),this.widgetElement.innerHTML=i,this.widgetElement.classList.add("cover-list"),this.rangeInput=this.widgetElement.querySelector(".cover-slider input"),this.widgetWidth=parseInt(this.widgetElement.clientWidth,10),this.widgetWidth%2!=0&&this.widgetWidth--,this.widgetHeight=parseInt(this.widgetElement.clientHeight,10),this.widgetHeight%2!=0&&this.widgetHeight--,this.calculatePositions();try{this.initCanvas()}catch(t){console.error("Error when trying to initialize cover list.",t)}const e=this;this.widgetElement.querySelector(".cover-previous").addEventListener("click",this.goToPrevious.bind(this)),this.widgetElement.querySelector(".cover-next").addEventListener("click",this.goToNext.bind(this)),this.rangeInput.addEventListener("input",function(){e.goToIndex(this.value)})},CoverList.prototype.calculatePositions=function(){if(0==this.elements.length)this.positions=[];else if(1==this.elements.length){const t=(this.widgetHeight-this.boxHeight)/2+this.yOffset,i=(this.widgetWidth-this.boxWidth)/2;this.positions.push({delta:0,factor:0,width:this.boxWidth,height:this.boxHeight,zindex:1,top:t,offset:i})}else{const t=this.elements.length;let i=1;t<5&&(i=.75,t<3&&(i=.5));for(let e=t;e>=0;e--){const s=t-e;let o=s/t;o=Math.sqrt(o)*i;const h=this.boxWidth*(1-o*this.minSize),n=this.boxHeight*(1-o*this.minSize),a=t-s,l=(this.widgetHeight-n)/2+this.yOffset,r=(this.widgetWidth-h)/2+o*(this.widgetWidth-h)/2;this.positions.push({delta:s,factor:o,width:h,height:n,zindex:a,top:l,offset:r})}}},CoverList.prototype.hideLoading=function(){this.widgetElement.querySelector(".cover-loading").style.display="none"},CoverList.prototype.initCanvas=function(){const t=this.widgetElement.querySelector("canvas");t.setAttribute("width",this.widgetWidth),t.setAttribute("height",this.widgetHeight),this.width=t.width,this.height=t.height,this.ctx=t.getContext("2d"),this.boxes=[],this.boxesDict={},this.nbImagesLoaded=0,this.imagesLoaded=!1;const i=this;t.addEventListener("click",function(e){let s=t,o=0,h=0;for(;null!=s&&null!=s;)o+=s.offsetLeft,h+=s.offsetTop,s=s.offsetParent;const n=e.pageX-o,a=e.pageY-h;i.onCanvasClick(n,a)});for(let t=0;t=this.elements.length&&(this.imagesLoaded=!0,this.hideLoading(),this.drawBoxes())},CoverList.prototype.goToPrevious=function(){this.goToIndex(this.selected-1)},CoverList.prototype.goToNext=function(){this.goToIndex(this.selected+1)},CoverList.prototype.goToIndex=function(t){if(t<0||t>this.elements.length-1||t==this.selected)return;this.selected=t;const i=this.animation.duration/this.animation.interval;for(let t=0;t=this.animation.duration)return;for(let t=0;ti&&(o=i,n=i/h),n>e&&(n=e,o=e*h),s.drawImage(t,this.padding+Math.floor((i-o)/2),this.padding+Math.floor((e-n)/2),o,n)}if(this.title){s.fillStyle="rgba(0, 0, 0, 0.8)",s.fillRect(this.padding,this.padding+e-this.titleH,i,this.titleH);const t=16;s.font="italic "+t+"px Arial",s.fillStyle="#fff",s.textAlign="center",s.textBaseline="middle";const h=this.padding+Math.floor(i/2);let o;const n=i-10;let a="",l="",r=!0;if(s.measureText(this.title).width>n){const t=this.title.split(" ");let i=t.length;for(let e=0;en){if(!a)for(let t=0;tn-20){a+=" ...",r=!1;break}a+=i[t]}break}a+=" "+i,t.shift()}if(r){i=t.length;for(let e=0;en-20){if(l)l+=" ...";else for(let t=0;tn-20){l+=" ...";break}l+=i[t]}break}l+=" "+i,t.shift()}}}else a=this.title;a&&l?(o=this.padding+e-Math.floor((this.titleH+t)/2),s.fillText(a,h,o),o+=4+t,s.fillText(l,h,o)):(o=this.padding+e-Math.floor(this.titleH/2),s.fillText(a,h,o))}s.save(),this.canvasR=document.createElement("canvas"),this.canvasR.width=this.bw,this.canvasR.height=this.bh;const h=this.canvasR.getContext("2d");h.drawImage(this.canvas,0,0,this.bw,this.bh);const o=h.createLinearGradient(0,0,0,this.bh);o.addColorStop(0,"rgba(0, 0, 0, 1)"),o.addColorStop(.5,"rgba(0, 0, 0, 1)"),o.addColorStop(1,"rgba(0, 0, 0, 0.7)"),h.globalCompositeOperation="destination-out",h.fillStyle=o,h.fillRect(0,0,this.bw,this.bh),null!=this.callback&&this.callback(this.id,!1)},CoverCanvasBox.prototype.setTarget=function(t){const i=t.steps?t.steps:50;for(let e=0;e=this.x&&t<=this.x+this.w&&i>=this.y&&i<=this.y+this.h},CoverList.prototype.addElement=function(t){const i={index:this.elements.length,title:"No title",thumb:"",url:""};for(const e in t)i[e]=t[e];this.elements.push(i)},CoverList.prototype.initCoverList=function(){this.selected<0?this.selected=Math.floor(this.elements.length/2):this.selected>=this.elements.length&&(this.selected=this.elements.length-1);let t="";for(const i of this.elements)t+='
  • '+jsu.escapeHTML(i.title)+"
  • ";const i='
      '+t+'
    ';this.widgetElement=document.querySelector(this.widgetPlace),this.widgetElement.innerHTML=i,this.widgetElement.classList.add("cover-list"),this.rangeInput=this.widgetElement.querySelector(".cover-slider input"),this.widgetWidth=parseInt(this.widgetElement.clientWidth,10),this.widgetWidth%2!=0&&this.widgetWidth--,this.widgetHeight=parseInt(this.widgetElement.clientHeight,10),this.widgetHeight%2!=0&&this.widgetHeight--,this.calculatePositions();try{this.initCanvas()}catch(t){console.error("Error when trying to initialize cover list.",t)}const e=this;this.widgetElement.querySelector(".cover-previous").addEventListener("click",this.goToPrevious.bind(this)),this.widgetElement.querySelector(".cover-next").addEventListener("click",this.goToNext.bind(this)),this.rangeInput.addEventListener("input",function(){e.goToIndex(this.value)})},CoverList.prototype.calculatePositions=function(){if(0==this.elements.length)this.positions=[];else if(1==this.elements.length){const t=(this.widgetHeight-this.boxHeight)/2+this.yOffset,i=(this.widgetWidth-this.boxWidth)/2;this.positions.push({delta:0,factor:0,width:this.boxWidth,height:this.boxHeight,zindex:1,top:t,offset:i})}else{const t=this.elements.length;let i=1;t<5&&(i=.75,t<3&&(i=.5));for(let e=t;e>=0;e--){const s=t-e;let h=s/t;h=Math.sqrt(h)*i;const o=this.boxWidth*(1-h*this.minSize),n=this.boxHeight*(1-h*this.minSize),a=t-s,l=(this.widgetHeight-n)/2+this.yOffset,r=(this.widgetWidth-o)/2+h*(this.widgetWidth-o)/2;this.positions.push({delta:s,factor:h,width:o,height:n,zindex:a,top:l,offset:r})}}},CoverList.prototype.hideLoading=function(){this.widgetElement.querySelector(".cover-loading").style.display="none"},CoverList.prototype.initCanvas=function(){const t=this.widgetElement.querySelector("canvas");t.setAttribute("width",this.widgetWidth),t.setAttribute("height",this.widgetHeight),this.width=t.width,this.height=t.height,this.ctx=t.getContext("2d"),this.boxes=[],this.boxesDict={},this.nbImagesLoaded=0,this.imagesLoaded=!1;const i=this;t.addEventListener("click",function(e){let s=t,h=0,o=0;for(;null!=s&&null!=s;)h+=s.offsetLeft,o+=s.offsetTop,s=s.offsetParent;const n=e.pageX-h,a=e.pageY-o;i.onCanvasClick(n,a)});for(let t=0;t=this.elements.length&&(this.imagesLoaded=!0,this.hideLoading(),this.drawBoxes())},CoverList.prototype.goToPrevious=function(){this.goToIndex(this.selected-1)},CoverList.prototype.goToNext=function(){this.goToIndex(this.selected+1)},CoverList.prototype.goToIndex=function(t){if(t<0||t>this.elements.length-1||t==this.selected)return;this.selected=t;const i=this.animation.duration/this.animation.interval;for(let t=0;t=this.animation.duration)return;for(let t=0;t innerw) { @@ -90,10 +91,10 @@ CoverCanvasBox.prototype.onLoad = function (img, success) { ctx.drawImage(img, this.padding + Math.floor((innerw - imgW) / 2), this.padding + Math.floor((innerh - imgH) / 2), imgW, imgH); } if (this.title) { - // draw black mask - ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; + // Draw black mask + ctx.fillStyle = 'rgba(0, 0, 0, 0.8)'; ctx.fillRect(this.padding, this.padding + innerh - this.titleH, innerw, this.titleH); - // get title position + // Get title position const fontHeight = 16; ctx.font = 'italic ' + fontHeight + 'px Arial'; ctx.fillStyle = '#fff'; @@ -105,14 +106,14 @@ CoverCanvasBox.prototype.onLoad = function (img, success) { let lineTop = '', lineBot = '', needBot = true; const size = ctx.measureText(this.title).width; if (size > maxW) { - // text is too long, use 2 lines + // Text is too long, use 2 lines const splitted = this.title.split(' '); let words = splitted.length; for (let i = 0; i < words; i++) { const word = splitted[0]; if (ctx.measureText(lineTop + ' ' + word).width > maxW) { if (!lineTop) { - // the word is too long + // The word is too long for (let j = 0; j < word.length; j++) { if (ctx.measureText(lineTop + word[j]).width > maxW - 20) { lineTop += ' ...'; @@ -133,7 +134,7 @@ CoverCanvasBox.prototype.onLoad = function (img, success) { const word = splitted[0]; if (ctx.measureText(lineBot + ' ' + word).width > maxW - 20) { if (!lineBot) { - // the word is too long + // The word is too long for (let j = 0; j < word.length; j++) { if (ctx.measureText(lineBot + word[j]).width > maxW - 20) { lineBot += ' ...'; @@ -153,7 +154,7 @@ CoverCanvasBox.prototype.onLoad = function (img, success) { } else { lineTop = this.title; } - // write title + // Write title if (lineTop && lineBot) { textY = this.padding + innerh - Math.floor((this.titleH + fontHeight) / 2); ctx.fillText(lineTop, textX, textY); @@ -166,6 +167,21 @@ CoverCanvasBox.prototype.onLoad = function (img, success) { } ctx.save(); + // Create reflection canvas + this.canvasR = document.createElement('canvas'); + this.canvasR.width = this.bw; + this.canvasR.height = this.bh; + + const ctxR = this.canvasR.getContext('2d'); + ctxR.drawImage(this.canvas, 0, 0, this.bw, this.bh); + const gradR = ctxR.createLinearGradient(0, 0, 0, this.bh); + gradR.addColorStop(0, 'rgba(0, 0, 0, 1)'); + gradR.addColorStop(0.5, 'rgba(0, 0, 0, 1)'); + gradR.addColorStop(1, 'rgba(0, 0, 0, 0.7)'); + ctxR.globalCompositeOperation = 'destination-out'; + ctxR.fillStyle = gradR; + ctxR.fillRect(0, 0, this.bw, this.bh); + if (this.callback != null) { this.callback(this.id, false); } @@ -195,23 +211,16 @@ CoverCanvasBox.prototype.draw = function (ctx) { if (!this.canvas) { return; } - const xr = this.x; - const yr = -this.y - 2 * this.h; + // Clear area to avoid reflects overlapping + ctx.clearRect(this.x, this.y, this.w, 2 * this.h); ctx.drawImage(this.canvas, this.x, this.y, this.w, this.h); - ctx.save(); // save transformation states + ctx.save(); // Save transformation states - // draw reflection + // Draw reflection ctx.scale(1, -1); - ctx.drawImage(this.canvas, xr, yr, this.w, this.h); - // fade reflection - const grad = ctx.createLinearGradient(0, yr, 0, yr + this.h); - grad.addColorStop(0, 'rgba(255, 255, 255, 1)'); - grad.addColorStop(1, 'rgba(255, 255, 255, 0.5)'); - ctx.fillStyle = grad; - ctx.fillRect(this.x, yr, this.w, this.h); - - ctx.restore(); // retore transformation states + ctx.drawImage(this.canvasR, this.x, -this.y - 2 * this.h, this.w, this.h); + ctx.restore(); // Retore transformation states }; CoverCanvasBox.prototype.contains = function (x, y) { return x >= this.x && x <= this.x + this.w && y >= this.y && y <= this.y + this.h; @@ -222,7 +231,7 @@ CoverCanvasBox.prototype.contains = function (x, y) { * CoverList class */ function CoverList (options) { - // params + // Params this.widgetPlace = '#cover_list'; this.yOffset = -10; this.padding = 3; @@ -232,7 +241,7 @@ function CoverList (options) { this.selected = -1; this.color = '#666'; this.boxBg = '#fff'; - // vars + // Vars this.widgetElement = null; this.widgetWidth = 0; this.widgetHeight = 0; @@ -315,8 +324,8 @@ CoverList.prototype.initCoverList = function () { this.widgetElement.classList.add('cover-list'); this.rangeInput = this.widgetElement.querySelector('.cover-slider input'); - // use only integer and divisible by two values for width and height - // this is done to avoid having a blurry centered image + // Use only integer and divisible by two values for width and height + // This is done to avoid having a blurry centered image this.widgetWidth = parseInt(this.widgetElement.clientWidth, 10); if (this.widgetWidth % 2 != 0) { this.widgetWidth--; @@ -333,7 +342,7 @@ CoverList.prototype.initCoverList = function () { console.error('Error when trying to initialize cover list.', err); } - // init events + // Init events const obj = this; this.widgetElement.querySelector('.cover-previous').addEventListener('click', this.goToPrevious.bind(this)); this.widgetElement.querySelector('.cover-next').addEventListener('click', this.goToNext.bind(this)); @@ -406,12 +415,12 @@ CoverList.prototype.initCanvas = function () { this.nbImagesLoaded = 0; this.imagesLoaded = false; - // click events + // Click events const obj = this; canvasEle.addEventListener('click', function (evt) { let dom = canvasEle, xOffset = 0, yOffset = 0; - // get canvas offset + // Get canvas offset while (dom != null && dom != undefined) { xOffset += dom.offsetLeft; yOffset += dom.offsetTop; @@ -504,23 +513,23 @@ CoverList.prototype.addBox = function (box) { box.loadImage(); }; CoverList.prototype.drawBoxes = function () { - // get background first + // Get background first this.boxes = this.boxes.sort(function (a, b) { return a.z - b.z; }); - // clear canvas + // Clear canvas this.ctx.clearRect(0, 0, this.width, this.height); - // draw boxes + // Draw boxes for (let i = 0; i < this.boxes.length; i++) { this.boxes[i].draw(this.ctx); } }; CoverList.prototype.onCanvasClick = function (x, y) { - // get foreground first + // Get foreground first const boxes = this.boxes.sort(function (a, b) { return b.z - a.z; }); - // get selected + // Get selected for (let i = 0; i < boxes.length; i++) { if (boxes[i].contains(x, y)) { this.selectBox(boxes[i]); @@ -530,10 +539,10 @@ CoverList.prototype.onCanvasClick = function (x, y) { }; CoverList.prototype.selectBox = function (box) { if (box.id == this.selected) { - // go to box url + // Go to box url window.location = box.url; } else { - // move boxes + // Move boxes this.goToIndex(box.id); } }; @@ -550,14 +559,14 @@ CoverList.prototype.animateMovementLoop = function () { if (this.animation.currentTime >= this.animation.duration) { return; } - // draw next step + // Draw next step for (let i = 0; i < this.boxes.length; i++) { this.boxes[i].increment(); } if (this.imagesLoaded) { this.drawBoxes(); } - // programm next draw + // Programm next draw this.animation.currentTime += this.animation.interval; const obj = this; this.animation.timeout = setTimeout(function () { diff --git a/test/index.html b/test/index.html index 9871aa9..ed2caf8 100644 --- a/test/index.html +++ b/test/index.html @@ -8,29 +8,33 @@