独立滑块和拼图,通过实例方法组合使用,滑块和拼图均通过指定元素容器加载内容 通过canvas 路径切片 <! DOCTYPE  html > < htmllang = " en" > < head> < metacharset = " UTF-8" > < title> </ title> < style> .slider-validator  { position :  relative; display :  inline-block; } #refresh  { position :  absolute; top :  10px; right :  20px; width :  20px; height :  20px; background-image :  url ( "data:image/svg+xml;base64,PHN2ZyB0PSIxNjY3MzYwNTYxMTA1IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjI3MTEiIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4Ij48cGF0aCBkPSJNNTcxLjczMzMzMyAyMjcuODRWMTUxLjQ2NjY2N2MwLTI1LjE3MzMzMy0xOS42MjY2NjctMzUuNDEzMzMzLTMzLjcwNjY2Ni0yNS4xNzMzMzRMMzI3LjY4IDI3Mi42NGEyMS4zMzMzMzMgMjEuMzMzMzMzIDAgMCAwIDAgMzUuNDEzMzMzbDIxMC4zNDY2NjcgMTQ2Ljc3MzMzNGEyMS43NiAyMS43NiAwIDAgMCAzMy43MDY2NjYtMTcuOTJWMzg2LjU2YTIxLjMzMzMzMyAyMS4zMzMzMzMgMCAwIDEgMjMuMDQtMjEuMzMzMzMzIDI3MS43ODY2NjcgMjcxLjc4NjY2NyAwIDAgMSAyMzkuMzYgMjEwLjM0NjY2NiAyNjAuMjY2NjY3IDI2MC4yNjY2NjcgMCAwIDAgNi40LTU4LjAyNjY2NiAyNzEuMzYgMjcxLjM2IDAgMCAwLTI0OC43NDY2NjYtMjY4LjggMjAuOTA2NjY3IDIwLjkwNjY2NyAwIDAgMS0yMC4wNTMzMzQtMjAuOTA2NjY3ek00NDMuNzMzMzMzIDc5MC42MTMzMzN2ODUuMzMzMzM0YTIxLjMzMzMzMyAyMS4zMzMzMzMgMCAwIDAgMzMuMjggMTcuNDkzMzMzbDIxMS4yLTE0Ny4yYTIxLjMzMzMzMyAyMS4zMzMzMzMgMCAwIDAgMC0zNC41NmwtMjExLjItMTQ3LjJhMjAuOTA2NjY3IDIwLjkwNjY2NyAwIDAgMC0zMy4yOCAxNy4wNjY2Njd2NTEuMmEyMC45MDY2NjcgMjAuOTA2NjY3IDAgMCAxLTIyLjYxMzMzMyAyMS4zMzMzMzMgMjcxLjc4NjY2NyAyNzEuNzg2NjY3IDAgMCAxLTIzOS43ODY2NjctMjEwLjM0NjY2NyAyNjAuMjY2NjY3IDI2MC4yNjY2NjcgMCAwIDAtNi40IDU4LjAyNjY2N0EyNzAuNTA2NjY3IDI3MC41MDY2NjcgMCAwIDAgNDIzLjY4IDc2OGEyMS43NiAyMS43NiAwIDAgMSAyMC4wNTMzMzMgMjIuNjEzMzMzeiIgZmlsbD0iIzY2NjY2NiIgcC1pZD0iMjcxMiI+PC9wYXRoPjwvc3ZnPg==" ) ; background-repeat :  no-repeat; background-position :  center center; background-size :  cover; cursor :  pointer; filter :  brightness ( 0.1) ; } .loading-mask  { position :  absolute; left :  0; top :  0; width :  100%; height :  100%; display :  flex; align-items :  center; justify-content :  center; z-index :  2; background :  rgba ( 255,  255,  255,  0.75) ; } .loading-mask svg  { animation :  ani-loading 3s linear infinite; } @keyframes  ani-loading{ 0%  { transform :  rotate ( 0) ; } 100%  { transform :  rotate ( 360deg) ; } } #puzzle  { position :  relative; border-radius :  15px; overflow :  hidden; } #drag-track  { position :  relative; height :  40px; border :  1px solid #ddd; border-radius :  3px; background :  #fff; text-align :  center; line-height :  40px; user-select :  none; } .drag-wrap  { position :  absolute; top :  0; left :  0; background :  rgba ( 224,  224,  224,  0.71) ; } .drag-wrap.success  { background :  rgba ( 13,  143,  255,  0.82) ; } .drag-wrap.fail  { background :  rgba ( 255,  110,  125,  0.83) ; } .drag-handle  { display :  flex; align-items :  center; justify-content :  center; font-size :  20px; width :  40px; height :  40px; border-radius :  3px; box-shadow :  #9b9b9b 1px 1px 4px; background :  #fff; cursor :  pointer; } .drag-handle:after  { content :  '' ; display :  block; width :  100%; height :  100%; background :  url ( "data:image/svg+xml;base64,PHN2ZyB0PSIxNjY3MzYwNjgyODU1IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjM5MTkiIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4Ij48cGF0aCBkPSJNNTY3LjMyNTA1IDU0Ny4xODUzNmMyMC45NzA2MTQtMjEuNDc5MTk3IDIwLjk3MDYxNC01Ni4zMDc0MjQgMC03Ny43OTA3MTRMMTg1LjI1MTE2OCA3Ny4xMTUzMzJjLTIwLjk3MTYzNy0yMS40NzcxNS01NC45NzUwNzktMjEuNDc3MTUtNzUuOTQ4NzYzIDAtMjAuOTczNjg0IDIxLjQ4NDMxNC0yMC45NzM2ODQgNTYuMzA5NDcgMCA3Ny43OTM3ODRsMzQ0LjE4ODAxNiAzNTMuMzgzNDQ2LTM0NC4xODgwMTYgMzUzLjM4NDQ2OWMtMjAuOTczNjg0IDIxLjQ4NDMxNC0yMC45NzM2ODQgNTYuMzExNTE3IDAgNzcuNzkyNzYgMjAuOTcxNjM3IDIxLjQ4MjI2NyA1NC45NzUwNzkgMjEuNDgyMjY3IDc1Ljk0ODc2MyAwbDM4Mi4wNzI4NTgtMzkyLjI4MDMzNyAwLjAwMTAyNC0wLjAwNDA5NHpNNDQwLjYwODAyIDE1NC45MDgwOTJsMzQ0LjE4NTk3IDM1My4zODM0NDYtMzQ0LjE4NTk3IDM1My4zODU0OTNjLTIwLjk3MzY4NCAyMS40ODQzMTQtMjAuOTczNjg0IDU2LjMxMTUxNyAwIDc3Ljc5Mjc2IDIwLjk3MjY2MSAyMS40ODIyNjcgNTQuOTc1MDc5IDIxLjQ4MjI2NyA3NS45NDk3ODYgMGwzODIuMDc0OTA1LTM5Mi4yODEzNjFjMjAuOTY2NTIxLTIxLjQ3ODE3NCAyMC45NjY1MjEtNTYuMzA3NDI0IDAtNzcuNzkwNzE0TDUxNi41NTU3NTkgNzcuMTE1MzMyYy0yMC45NzI2NjEtMjEuNDc3MTUtNTQuOTc1MDc5LTIxLjQ3NzE1LTc1Ljk0OTc4NiAwLTIwLjk3MTYzNyAyMS40ODMyOS0yMC45NzE2MzcgNTYuMzA5NDcgMC4wMDIwNDcgNzcuNzkyNzZ6IiBwLWlkPSIzOTIwIj48L3BhdGg+PC9zdmc+" ) ; opacity :  0.5; } .drag-handle:hover:after  { opacity :  1; } </ style> </ head> < body> < divclass = " slider-validator" > < divid = " puzzle" > </ div> < divclass = " loading-mask" > < svgviewBox = " 0 0 1024 1024" version = " 1.1" xmlns = " http://www.w3.org/2000/svg" width = " 40" height = " 40" > < pathd = " M484 64h56c4.42 0 8 3.58 8 8v248c0 4.42-3.58 8-8 8h-56c-4.42 0-8-3.58-8-8V72c0-4.42 3.58-8 8-8z m0 632h56c4.42 0 8 3.58 8 8v248c0 4.42-3.58 8-8 8h-56c-4.42 0-8-3.58-8-8V704c0-4.42 3.58-8 8-8z m324.98-520.58l39.6 39.6c3.12 3.12 3.12 8.19 0 11.31L673.22 401.69c-3.12 3.12-8.19 3.12-11.31 0l-39.6-39.6c-3.12-3.12-3.12-8.19 0-11.31l175.36-175.36c3.13-3.13 8.19-3.13 11.31 0zM362.09 622.31l39.6 39.6c3.12 3.12 3.12 8.19 0 11.31L226.33 848.58c-3.12 3.12-8.19 3.12-11.31 0l-39.6-39.6c-3.12-3.12-3.12-8.19 0-11.31l175.36-175.36a7.985 7.985 0 0 1 11.31 0zM960 484v56c0 4.42-3.58 8-8 8H704c-4.42 0-8-3.58-8-8v-56c0-4.42 3.58-8 8-8h248c4.42 0 8 3.58 8 8z m-632 0v56c0 4.42-3.58 8-8 8H72c-4.42 0-8-3.58-8-8v-56c0-4.42 3.58-8 8-8h248c4.42 0 8 3.58 8 8z m520.58 324.98l-39.6 39.6c-3.12 3.12-8.19 3.12-11.31 0L622.31 673.22c-3.12-3.12-3.12-8.19 0-11.31l39.6-39.6c3.12-3.12 8.19-3.12 11.31 0l175.36 175.36c3.13 3.13 3.13 8.19 0 11.31zM401.69 362.09l-39.6 39.6c-3.12 3.12-8.19 3.12-11.31 0L175.42 226.33c-3.12-3.12-3.12-8.19 0-11.31l39.6-39.6c3.12-3.12 8.19-3.12 11.31 0l175.36 175.36a7.985 7.985 0 0 1 0 11.31z" > </ path> </ svg> </ div> < divid = " refresh" > </ div> </ div> < br> < divid = " drag-track" style = " width :  400px" > </ div> < script> const  PI  =  Math. PI ,  PI_05  =  PI  *  0.5 ,  PI_15  =  PI  *  1.5 
const  _Vertices =  [ 5 ,  1 ,  5 ,  1 ,  3 ,  0 ,  3 ,  2 ,  5 ,  5 ,  5 ,  5 ,  6 ,  3 ,  4 ,  3 ,  1 ,  5 ,  1 ,  5 ,  3 ,  6 ,  3 ,  4 ,  1 ,  1 ,  1 ,  1 ,  0 ,  3 ,  2 ,  3 ] 
const  createElement  =  ( tag,  attrs )  =>  { let  $el =  tag instanceof  Node  ?  tag :  document. createElement ( tag) if  ( attrs)  { Object. keys ( attrs) . forEach ( k  =>  $el. setAttribute ( k,  attrs[ k] ) ) } return  $el
} 
class  Puzzle  { cr =  5 co =  2 Radian =  null DO  =  null clipWidth =  0 clipHeight =  0 bgWidth =  0 bgHeight =  0 offsetLeft =  0 $Indicator =  null constructor ( rootSelector )  { this . $Root =  document. querySelector ( rootSelector) } init ( {  cr =  5 ,  co =  2 ,  clipWidth =  60 ,  clipHeight =  60 ,  bgWidth =  300 ,  bgHeight =  200  } )  { const  vm =  this vm. cr =  crvm. co =  covm. clipWidth =  clipWidthvm. clipHeight =  clipHeightvm. bgWidth =  bgWidthvm. bgHeight =  bgHeightconst  _CR =  Math. acos ( ( cr -  co)  /  cr) vm. Radian =  [ [ PI_05  +  _CR,  PI_05  -  _CR] ,  [ PI  +  _CR,  PI  -  _CR] ,  [ _CR -  PI_05 ,  PI_15  -  _CR] ,  [ _CR,  - _CR] , [ PI_15  -  _CR,  _CR -  PI_05 ] ,  [ - _CR,  _CR] ,  [ PI_05  -  _CR,  PI_05  +  _CR] ,  [ PI  -  _CR,  PI  +  _CR] , ] vm. DO  =  co -  cr} setOffset ( offsetLeft )  { this . $Indicator. style. left =  ` ${ offsetLeft} px ` } load ( src )  { const  vm =  this const  {  bgWidth,  bgHeight,  clipWidth,  clipHeight,  DO ,  cr,  Radian }  =  vmif  ( ! src)  { src =  ` https://picsum.photos/ ${ bgWidth} / ${ bgHeight} ` } const  $ClipBg =  createElement ( 'canvas' ,  {  width :  bgWidth,  height :  bgHeight } ) const  $ClipBlock =  createElement ( 'canvas' ,  {  width :  bgWidth,  height :  bgHeight } ) const  $Indicator =  createElement ( 'img' ) vm. $Root. style. cssText +=  ` ;width: ${ bgWidth} px;height: ${ bgHeight} px ` . $Root. innerHTML =  '' vm. $Root. append ( $ClipBg,  $Indicator) const  boxCtx =  $ClipBg. getContext ( '2d' ) const  blockCtx =  $ClipBlock. getContext ( '2d' ) const  left =  bgWidth /  2  +  Math. random ( )  *  ( bgWidth -  clipWidth -  cr *  2  -  bgWidth /  2 ) const  top =  bgHeight /  2  -  clipHeight /  2 const  xa =  [ left +  DO ,  left,  left -  DO ,  left +  clipWidth /  2 ,  left +  clipWidth +  DO ,  left +  clipWidth,  left +  clipWidth -  DO ] const  ya =  [ top +  DO ,  top,  top -  DO ,  top +  clipHeight /  2 ,  top +  clipHeight +  DO ,  top +  clipHeight,  top +  clipHeight -  DO ] const  _MODES =  [ 1 ,  1 ,  1 ,  1 ] . map ( d  =>  Math. random ( )  >  0.5  ?  1  :  - 1 ) let  offsetLeft =  _MODES[ 3 ]  ===  1  ?  left -  ( cr -  DO )  :  left; $Indicator. style =  ` left:0;position:absolute;filter:drop-shadow(2px 5px 4px #000);transform:translateX(- ${ offsetLeft} px) ` . offsetLeft =  offsetLeftvm. $Indicator =  $Indicatorlet  image =  new  Image ( ) image. crossOrigin =  'Anonymous' ; image. src =  srcreturn  new  Promise ( ( resolve,  reject )  =>  { image. onerror =  rejectimage. onload  =  _  =>  { const  draw  =  ctx  =>  { ctx. globalCompositeOperation =  'destination-over' ctx. moveTo ( left,  top) ; _MODES. forEach ( ( type,  index )  =>  { let  _v =  _Vertices. slice ( index *  8 ,  ( index +  1 )  *  8 ) if  ( type ===  0 )  { return  ctx. lineTo ( xa[ _v[ 0 ] ] ,  ya[ _v[ 1 ] ] ) ; } if  ( type >  0 )  { ctx. arc ( xa[ _v[ 4 ] ] ,  ya[ _v[ 5 ] ] ,  cr,  ... ( Radian[ index] ) ,  false ) }  else  { ctx. arc ( xa[ _v[ 6 ] ] ,  ya[ _v[ 7 ] ] ,  cr,  ... ( Radian[ index +  4 ] ) ,  true ) } ctx. lineTo ( xa[ _v[ 2 ] ] ,  ya[ _v[ 3 ] ] ) } ) } draw ( blockCtx) blockCtx. clip ( ) blockCtx. drawImage ( image,  0 ,  0 ,  bgWidth,  bgHeight) draw ( boxCtx) boxCtx. fillStyle =  '#fff' boxCtx. fill ( ) boxCtx. drawImage ( image,  0 ,  0 ,  bgWidth,  bgHeight) $Indicator. src =  $ClipBlock. toDataURL ( 'image/png' ,  1 ) resolve ( ) } } ) } 
} class  Slider  { $DragWrap =  null lock =  false turingTest =  false constructor ( {  root,  endHandle,  moveHandle } )  { const  vm =  this let  $Root =  document. querySelector ( root) let  $DragWrap =  createElement ( 'div' ,  {  class :  'drag-wrap'  } ) let  $DragHandle =  createElement ( 'div' ,  {  class :  'drag-handle'  } ) $Root. innerHTML =  '<span>向右拖动滑块填充拼图</span>' $DragWrap. append ( $DragHandle) $Root. append ( $DragWrap) vm. $DragWrap =  $DragWraplet  startX =  0 ,  startY =  0 ,  leftResult =  0 const  MMH  =  e  =>  { leftResult =  Math. min ( Math. max ( 0 ,  e. clientX -  startX) ,  $Root. clientWidth -  $DragHandle. offsetWidth) if  ( e. clientY -  startY !==  0 )  { vm. turingTest =  true } $DragWrap. style. paddingLeft =  ` ${ leftResult} px ` moveHandle . call ( this ,  leftResult) } const  MUH  =  e  =>  { $Root. removeEventListener ( 'mousemove' ,  MMH ) $Root. removeEventListener ( 'mouseup' ,  MUH ) endHandle . call ( this ,  leftResult) vm. lock =  true } $Root. addEventListener ( 'mousedown' ,  e  =>  { if  ( ! vm. lock)  { startX =  e. clientXstartY =  e. clientY$Root. addEventListener ( 'mousemove' ,  MMH ) $Root. addEventListener ( 'mouseup' ,  MUH ) } } ) } reset ( )  { this . $DragWrap. classList. remove ( 'fail' ,  'success' ) this . $DragWrap. style. paddingLeft =  ` 0px ` this . lock =  false } setSuccess ( )  { this . $DragWrap. classList. remove ( 'fail' ) this . $DragWrap. classList. add ( 'success' ) } setFail ( )  { this . $DragWrap. classList. remove ( 'success' ) this . $DragWrap. classList. add ( 'fail' ) } 
} function  loadGroupPlugin ( )  { let  pz =  new  Puzzle ( '#puzzle' ) pz. init ( { cr :  8 , co :  4 , clipWidth :  40 , clipHeight :  40 , bgWidth :  400 , bgHeight :  300 , } ) let  slider =  new  Slider ( { root :  '#drag-track' , endHandle ( left )  { if  ( ! this . turingTest)  { slider. setFail ( ) }  else  { if  ( Math. abs ( pz. offsetLeft -  left)  <  10 )  { slider. setSuccess ( ) }  else  { slider. setFail ( ) } } setTimeout ( _  =>  { slider. reset ( ) load ( ) } ,  2000 ) } , moveHandle ( left )  { pz. setOffset ( left) } } ) let  $loading =  document. querySelector ( '.loading-mask' ) let  load  =  src  =>  { $loading. style. display =  'flex' pz. load ( src) . then ( d  =>  { slider. lock =  false $loading. style. display =  'none' } ) . catch ( d  =>  { $loading. innerText =  '获取图片失败' slider. lock =  true } ) } load ( './c.jpg' ) let  $change =  document. getElementById ( 'refresh' ) $change. addEventListener ( 'click' ,  _  =>  { load ( ) } ) 
} loadGroupPlugin ( ) </ script> </ body> </ html>